Detailed changes
@@ -6,9 +6,12 @@ use gpui::{
WeakView, WindowContext,
};
use picker::{Picker, PickerDelegate};
-use std::cmp::{self, Reverse};
+use std::{
+ cmp::{self, Reverse},
+ sync::Arc,
+};
use theme::ActiveTheme;
-use ui::{v_stack, Label, StyledExt};
+use ui::{h_stack, v_stack, HighlightedLabel, KeyBinding, StyledExt};
use util::{
channel::{parse_zed_link, ReleaseChannel, RELEASE_CHANNEL},
ResultExt,
@@ -147,6 +150,10 @@ impl CommandPaletteDelegate {
impl PickerDelegate for CommandPaletteDelegate {
type ListItem = Div<Picker<Self>>;
+ fn placeholder_text(&self) -> Arc<str> {
+ "Execute a command...".into()
+ }
+
fn match_count(&self) -> usize {
self.matches.len()
}
@@ -296,11 +303,10 @@ impl PickerDelegate for CommandPaletteDelegate {
cx: &mut ViewContext<Picker<Self>>,
) -> Self::ListItem {
let colors = cx.theme().colors();
- let Some(command) = self
- .matches
- .get(ix)
- .and_then(|m| self.commands.get(m.candidate_id))
- else {
+ let Some(r#match) = self.matches.get(ix) else {
+ return div();
+ };
+ let Some(command) = self.commands.get(r#match.candidate_id) else {
return div();
};
@@ -312,63 +318,16 @@ impl PickerDelegate for CommandPaletteDelegate {
.rounded_md()
.when(selected, |this| this.bg(colors.ghost_element_selected))
.hover(|this| this.bg(colors.ghost_element_hover))
- .child(Label::new(command.name.clone()))
+ .child(
+ h_stack()
+ .justify_between()
+ .child(HighlightedLabel::new(
+ command.name.clone(),
+ r#match.positions.clone(),
+ ))
+ .children(KeyBinding::for_action(&*command.action, cx)),
+ )
}
-
- // fn render_match(
- // &self,
- // ix: usize,
- // mouse_state: &mut MouseState,
- // selected: bool,
- // cx: &gpui::AppContext,
- // ) -> AnyElement<Picker<Self>> {
- // let mat = &self.matches[ix];
- // let command = &self.actions[mat.candidate_id];
- // let theme = theme::current(cx);
- // let style = theme.picker.item.in_state(selected).style_for(mouse_state);
- // let key_style = &theme.command_palette.key.in_state(selected);
- // let keystroke_spacing = theme.command_palette.keystroke_spacing;
-
- // Flex::row()
- // .with_child(
- // Label::new(mat.string.clone(), style.label.clone())
- // .with_highlights(mat.positions.clone()),
- // )
- // .with_children(command.keystrokes.iter().map(|keystroke| {
- // Flex::row()
- // .with_children(
- // [
- // (keystroke.ctrl, "^"),
- // (keystroke.alt, "⌥"),
- // (keystroke.cmd, "⌘"),
- // (keystroke.shift, "⇧"),
- // ]
- // .into_iter()
- // .filter_map(|(modifier, label)| {
- // if modifier {
- // Some(
- // Label::new(label, key_style.label.clone())
- // .contained()
- // .with_style(key_style.container),
- // )
- // } else {
- // None
- // }
- // }),
- // )
- // .with_child(
- // Label::new(keystroke.key.clone(), key_style.label.clone())
- // .contained()
- // .with_style(key_style.container),
- // )
- // .contained()
- // .with_margin_left(keystroke_spacing)
- // .flex_float()
- // }))
- // .contained()
- // .with_style(style.container)
- // .into_any()
- // }
}
fn humanize_action_name(name: &str) -> String {
@@ -41,8 +41,8 @@ use git::diff_hunk_to_display;
use gpui::{
action, actions, div, point, px, relative, rems, size, uniform_list, AnyElement, AppContext,
AsyncWindowContext, BackgroundExecutor, Bounds, ClipboardItem, Component, Context,
- DispatchContext, EventEmitter, FocusHandle, FontFeatures, FontStyle, FontWeight,
- HighlightStyle, Hsla, InputHandler, Model, MouseButton, ParentElement, Pixels, Render,
+ EventEmitter, FocusHandle, FontFeatures, FontStyle, FontWeight, HighlightStyle, Hsla,
+ InputHandler, KeyContext, Model, MouseButton, ParentElement, Pixels, Render,
StatelessInteractive, Styled, Subscription, Task, TextStyle, UniformListScrollHandle, View,
ViewContext, VisualContext, WeakView, WindowContext,
};
@@ -646,7 +646,7 @@ pub struct Editor {
collapse_matches: bool,
autoindent_mode: Option<AutoindentMode>,
workspace: Option<(WeakView<Workspace>, i64)>,
- keymap_context_layers: BTreeMap<TypeId, DispatchContext>,
+ keymap_context_layers: BTreeMap<TypeId, KeyContext>,
input_enabled: bool,
read_only: bool,
leader_peer_id: Option<PeerId>,
@@ -1981,9 +1981,9 @@ impl Editor {
this
}
- fn dispatch_context(&self, cx: &AppContext) -> DispatchContext {
- let mut dispatch_context = DispatchContext::default();
- dispatch_context.insert("Editor");
+ fn dispatch_context(&self, cx: &AppContext) -> KeyContext {
+ let mut dispatch_context = KeyContext::default();
+ dispatch_context.add("Editor");
let mode = match self.mode {
EditorMode::SingleLine => "single_line",
EditorMode::AutoHeight { .. } => "auto_height",
@@ -1991,17 +1991,17 @@ impl Editor {
};
dispatch_context.set("mode", mode);
if self.pending_rename.is_some() {
- dispatch_context.insert("renaming");
+ dispatch_context.add("renaming");
}
if self.context_menu_visible() {
match self.context_menu.read().as_ref() {
Some(ContextMenu::Completions(_)) => {
- dispatch_context.insert("menu");
- dispatch_context.insert("showing_completions")
+ dispatch_context.add("menu");
+ dispatch_context.add("showing_completions")
}
Some(ContextMenu::CodeActions(_)) => {
- dispatch_context.insert("menu");
- dispatch_context.insert("showing_code_actions")
+ dispatch_context.add("menu");
+ dispatch_context.add("showing_code_actions")
}
None => {}
}
@@ -16,11 +16,11 @@ use anyhow::Result;
use collections::{BTreeMap, HashMap};
use gpui::{
black, hsla, point, px, relative, size, transparent_black, Action, AnyElement, AvailableSpace,
- BorrowAppContext, BorrowWindow, Bounds, ContentMask, Corners, DispatchContext, DispatchPhase,
- Edges, Element, ElementId, ElementInputHandler, Entity, FocusHandle, GlobalElementId, Hsla,
- InputHandler, KeyDownEvent, KeyListener, KeyMatch, Line, LineLayout, Modifiers, MouseButton,
- MouseDownEvent, MouseMoveEvent, MouseUpEvent, Pixels, ScrollWheelEvent, ShapedGlyph, Size,
- Style, TextRun, TextStyle, TextSystem, ViewContext, WindowContext, WrappedLineLayout,
+ BorrowAppContext, BorrowWindow, Bounds, ContentMask, Corners, DispatchPhase, Edges, Element,
+ ElementId, ElementInputHandler, Entity, FocusHandle, GlobalElementId, Hsla, InputHandler,
+ KeyContext, KeyDownEvent, KeyMatch, Line, LineLayout, Modifiers, MouseButton, MouseDownEvent,
+ MouseMoveEvent, MouseUpEvent, Pixels, ScrollWheelEvent, ShapedGlyph, Size, Style, TextRun,
+ TextStyle, TextSystem, ViewContext, WindowContext, WrappedLineLayout,
};
use itertools::Itertools;
use language::language_settings::ShowWhitespaceSetting;
@@ -2459,11 +2459,170 @@ impl Element<Editor> for EditorElement {
let dispatch_context = editor.dispatch_context(cx);
cx.with_element_id(cx.view().entity_id(), |global_id, cx| {
- cx.with_key_dispatch_context(dispatch_context, |cx| {
- cx.with_key_listeners(build_key_listeners(global_id), |cx| {
- cx.with_focus(editor.focus_handle.clone(), |_| {})
- });
- })
+ cx.with_key_dispatch(
+ dispatch_context,
+ Some(editor.focus_handle.clone()),
+ |_, cx| {
+ register_action(cx, Editor::move_left);
+ register_action(cx, Editor::move_right);
+ register_action(cx, Editor::move_down);
+ register_action(cx, Editor::move_up);
+ // on_action(cx, Editor::new_file); todo!()
+ // on_action(cx, Editor::new_file_in_direction); todo!()
+ register_action(cx, Editor::cancel);
+ register_action(cx, Editor::newline);
+ register_action(cx, Editor::newline_above);
+ register_action(cx, Editor::newline_below);
+ register_action(cx, Editor::backspace);
+ register_action(cx, Editor::delete);
+ register_action(cx, Editor::tab);
+ register_action(cx, Editor::tab_prev);
+ register_action(cx, Editor::indent);
+ register_action(cx, Editor::outdent);
+ register_action(cx, Editor::delete_line);
+ register_action(cx, Editor::join_lines);
+ register_action(cx, Editor::sort_lines_case_sensitive);
+ register_action(cx, Editor::sort_lines_case_insensitive);
+ register_action(cx, Editor::reverse_lines);
+ register_action(cx, Editor::shuffle_lines);
+ register_action(cx, Editor::convert_to_upper_case);
+ register_action(cx, Editor::convert_to_lower_case);
+ register_action(cx, Editor::convert_to_title_case);
+ register_action(cx, Editor::convert_to_snake_case);
+ register_action(cx, Editor::convert_to_kebab_case);
+ register_action(cx, Editor::convert_to_upper_camel_case);
+ register_action(cx, Editor::convert_to_lower_camel_case);
+ register_action(cx, Editor::delete_to_previous_word_start);
+ register_action(cx, Editor::delete_to_previous_subword_start);
+ register_action(cx, Editor::delete_to_next_word_end);
+ register_action(cx, Editor::delete_to_next_subword_end);
+ register_action(cx, Editor::delete_to_beginning_of_line);
+ register_action(cx, Editor::delete_to_end_of_line);
+ register_action(cx, Editor::cut_to_end_of_line);
+ register_action(cx, Editor::duplicate_line);
+ register_action(cx, Editor::move_line_up);
+ register_action(cx, Editor::move_line_down);
+ register_action(cx, Editor::transpose);
+ register_action(cx, Editor::cut);
+ register_action(cx, Editor::copy);
+ register_action(cx, Editor::paste);
+ register_action(cx, Editor::undo);
+ register_action(cx, Editor::redo);
+ register_action(cx, Editor::move_page_up);
+ register_action(cx, Editor::move_page_down);
+ register_action(cx, Editor::next_screen);
+ register_action(cx, Editor::scroll_cursor_top);
+ register_action(cx, Editor::scroll_cursor_center);
+ register_action(cx, Editor::scroll_cursor_bottom);
+ register_action(cx, |editor, _: &LineDown, cx| {
+ editor.scroll_screen(&ScrollAmount::Line(1.), cx)
+ });
+ register_action(cx, |editor, _: &LineUp, cx| {
+ editor.scroll_screen(&ScrollAmount::Line(-1.), cx)
+ });
+ register_action(cx, |editor, _: &HalfPageDown, cx| {
+ editor.scroll_screen(&ScrollAmount::Page(0.5), cx)
+ });
+ register_action(cx, |editor, _: &HalfPageUp, cx| {
+ editor.scroll_screen(&ScrollAmount::Page(-0.5), cx)
+ });
+ register_action(cx, |editor, _: &PageDown, cx| {
+ editor.scroll_screen(&ScrollAmount::Page(1.), cx)
+ });
+ register_action(cx, |editor, _: &PageUp, cx| {
+ editor.scroll_screen(&ScrollAmount::Page(-1.), cx)
+ });
+ register_action(cx, Editor::move_to_previous_word_start);
+ register_action(cx, Editor::move_to_previous_subword_start);
+ register_action(cx, Editor::move_to_next_word_end);
+ register_action(cx, Editor::move_to_next_subword_end);
+ register_action(cx, Editor::move_to_beginning_of_line);
+ register_action(cx, Editor::move_to_end_of_line);
+ register_action(cx, Editor::move_to_start_of_paragraph);
+ register_action(cx, Editor::move_to_end_of_paragraph);
+ register_action(cx, Editor::move_to_beginning);
+ register_action(cx, Editor::move_to_end);
+ register_action(cx, Editor::select_up);
+ register_action(cx, Editor::select_down);
+ register_action(cx, Editor::select_left);
+ register_action(cx, Editor::select_right);
+ register_action(cx, Editor::select_to_previous_word_start);
+ register_action(cx, Editor::select_to_previous_subword_start);
+ register_action(cx, Editor::select_to_next_word_end);
+ register_action(cx, Editor::select_to_next_subword_end);
+ register_action(cx, Editor::select_to_beginning_of_line);
+ register_action(cx, Editor::select_to_end_of_line);
+ register_action(cx, Editor::select_to_start_of_paragraph);
+ register_action(cx, Editor::select_to_end_of_paragraph);
+ register_action(cx, Editor::select_to_beginning);
+ register_action(cx, Editor::select_to_end);
+ register_action(cx, Editor::select_all);
+ register_action(cx, |editor, action, cx| {
+ editor.select_all_matches(action, cx).log_err();
+ });
+ register_action(cx, Editor::select_line);
+ register_action(cx, Editor::split_selection_into_lines);
+ register_action(cx, Editor::add_selection_above);
+ register_action(cx, Editor::add_selection_below);
+ register_action(cx, |editor, action, cx| {
+ editor.select_next(action, cx).log_err();
+ });
+ register_action(cx, |editor, action, cx| {
+ editor.select_previous(action, cx).log_err();
+ });
+ register_action(cx, Editor::toggle_comments);
+ register_action(cx, Editor::select_larger_syntax_node);
+ register_action(cx, Editor::select_smaller_syntax_node);
+ register_action(cx, Editor::move_to_enclosing_bracket);
+ register_action(cx, Editor::undo_selection);
+ register_action(cx, Editor::redo_selection);
+ register_action(cx, Editor::go_to_diagnostic);
+ register_action(cx, Editor::go_to_prev_diagnostic);
+ register_action(cx, Editor::go_to_hunk);
+ register_action(cx, Editor::go_to_prev_hunk);
+ register_action(cx, Editor::go_to_definition);
+ register_action(cx, Editor::go_to_definition_split);
+ register_action(cx, Editor::go_to_type_definition);
+ register_action(cx, Editor::go_to_type_definition_split);
+ register_action(cx, Editor::fold);
+ register_action(cx, Editor::fold_at);
+ register_action(cx, Editor::unfold_lines);
+ register_action(cx, Editor::unfold_at);
+ register_action(cx, Editor::fold_selected_ranges);
+ register_action(cx, Editor::show_completions);
+ register_action(cx, Editor::toggle_code_actions);
+ // on_action(cx, Editor::open_excerpts); todo!()
+ register_action(cx, Editor::toggle_soft_wrap);
+ register_action(cx, Editor::toggle_inlay_hints);
+ register_action(cx, Editor::reveal_in_finder);
+ register_action(cx, Editor::copy_path);
+ register_action(cx, Editor::copy_relative_path);
+ register_action(cx, Editor::copy_highlight_json);
+ register_action(cx, |editor, action, cx| {
+ editor
+ .format(action, cx)
+ .map(|task| task.detach_and_log_err(cx));
+ });
+ register_action(cx, Editor::restart_language_server);
+ register_action(cx, Editor::show_character_palette);
+ // on_action(cx, Editor::confirm_completion); todo!()
+ register_action(cx, |editor, action, cx| {
+ editor
+ .confirm_code_action(action, cx)
+ .map(|task| task.detach_and_log_err(cx));
+ });
+ // on_action(cx, Editor::rename); todo!()
+ // on_action(cx, Editor::confirm_rename); todo!()
+ // on_action(cx, Editor::find_all_references); todo!()
+ register_action(cx, Editor::next_copilot_suggestion);
+ register_action(cx, Editor::previous_copilot_suggestion);
+ register_action(cx, Editor::copilot_suggest);
+ register_action(cx, Editor::context_menu_first);
+ register_action(cx, Editor::context_menu_prev);
+ register_action(cx, Editor::context_menu_next);
+ register_action(cx, Editor::context_menu_last);
+ },
+ )
});
}
@@ -3998,212 +4157,14 @@ fn scale_horizontal_mouse_autoscroll_delta(delta: Pixels) -> f32 {
// }
// }
-fn build_key_listeners(
- global_element_id: GlobalElementId,
-) -> impl IntoIterator<Item = (TypeId, KeyListener<Editor>)> {
- [
- build_action_listener(Editor::move_left),
- build_action_listener(Editor::move_right),
- build_action_listener(Editor::move_down),
- build_action_listener(Editor::move_up),
- // build_action_listener(Editor::new_file), todo!()
- // build_action_listener(Editor::new_file_in_direction), todo!()
- build_action_listener(Editor::cancel),
- build_action_listener(Editor::newline),
- build_action_listener(Editor::newline_above),
- build_action_listener(Editor::newline_below),
- build_action_listener(Editor::backspace),
- build_action_listener(Editor::delete),
- build_action_listener(Editor::tab),
- build_action_listener(Editor::tab_prev),
- build_action_listener(Editor::indent),
- build_action_listener(Editor::outdent),
- build_action_listener(Editor::delete_line),
- build_action_listener(Editor::join_lines),
- build_action_listener(Editor::sort_lines_case_sensitive),
- build_action_listener(Editor::sort_lines_case_insensitive),
- build_action_listener(Editor::reverse_lines),
- build_action_listener(Editor::shuffle_lines),
- build_action_listener(Editor::convert_to_upper_case),
- build_action_listener(Editor::convert_to_lower_case),
- build_action_listener(Editor::convert_to_title_case),
- build_action_listener(Editor::convert_to_snake_case),
- build_action_listener(Editor::convert_to_kebab_case),
- build_action_listener(Editor::convert_to_upper_camel_case),
- build_action_listener(Editor::convert_to_lower_camel_case),
- build_action_listener(Editor::delete_to_previous_word_start),
- build_action_listener(Editor::delete_to_previous_subword_start),
- build_action_listener(Editor::delete_to_next_word_end),
- build_action_listener(Editor::delete_to_next_subword_end),
- build_action_listener(Editor::delete_to_beginning_of_line),
- build_action_listener(Editor::delete_to_end_of_line),
- build_action_listener(Editor::cut_to_end_of_line),
- build_action_listener(Editor::duplicate_line),
- build_action_listener(Editor::move_line_up),
- build_action_listener(Editor::move_line_down),
- build_action_listener(Editor::transpose),
- build_action_listener(Editor::cut),
- build_action_listener(Editor::copy),
- build_action_listener(Editor::paste),
- build_action_listener(Editor::undo),
- build_action_listener(Editor::redo),
- build_action_listener(Editor::move_page_up),
- build_action_listener(Editor::move_page_down),
- build_action_listener(Editor::next_screen),
- build_action_listener(Editor::scroll_cursor_top),
- build_action_listener(Editor::scroll_cursor_center),
- build_action_listener(Editor::scroll_cursor_bottom),
- build_action_listener(|editor, _: &LineDown, cx| {
- editor.scroll_screen(&ScrollAmount::Line(1.), cx)
- }),
- build_action_listener(|editor, _: &LineUp, cx| {
- editor.scroll_screen(&ScrollAmount::Line(-1.), cx)
- }),
- build_action_listener(|editor, _: &HalfPageDown, cx| {
- editor.scroll_screen(&ScrollAmount::Page(0.5), cx)
- }),
- build_action_listener(|editor, _: &HalfPageUp, cx| {
- editor.scroll_screen(&ScrollAmount::Page(-0.5), cx)
- }),
- build_action_listener(|editor, _: &PageDown, cx| {
- editor.scroll_screen(&ScrollAmount::Page(1.), cx)
- }),
- build_action_listener(|editor, _: &PageUp, cx| {
- editor.scroll_screen(&ScrollAmount::Page(-1.), cx)
- }),
- build_action_listener(Editor::move_to_previous_word_start),
- build_action_listener(Editor::move_to_previous_subword_start),
- build_action_listener(Editor::move_to_next_word_end),
- build_action_listener(Editor::move_to_next_subword_end),
- build_action_listener(Editor::move_to_beginning_of_line),
- build_action_listener(Editor::move_to_end_of_line),
- build_action_listener(Editor::move_to_start_of_paragraph),
- build_action_listener(Editor::move_to_end_of_paragraph),
- build_action_listener(Editor::move_to_beginning),
- build_action_listener(Editor::move_to_end),
- build_action_listener(Editor::select_up),
- build_action_listener(Editor::select_down),
- build_action_listener(Editor::select_left),
- build_action_listener(Editor::select_right),
- build_action_listener(Editor::select_to_previous_word_start),
- build_action_listener(Editor::select_to_previous_subword_start),
- build_action_listener(Editor::select_to_next_word_end),
- build_action_listener(Editor::select_to_next_subword_end),
- build_action_listener(Editor::select_to_beginning_of_line),
- build_action_listener(Editor::select_to_end_of_line),
- build_action_listener(Editor::select_to_start_of_paragraph),
- build_action_listener(Editor::select_to_end_of_paragraph),
- build_action_listener(Editor::select_to_beginning),
- build_action_listener(Editor::select_to_end),
- build_action_listener(Editor::select_all),
- build_action_listener(|editor, action, cx| {
- editor.select_all_matches(action, cx).log_err();
- }),
- build_action_listener(Editor::select_line),
- build_action_listener(Editor::split_selection_into_lines),
- build_action_listener(Editor::add_selection_above),
- build_action_listener(Editor::add_selection_below),
- build_action_listener(|editor, action, cx| {
- editor.select_next(action, cx).log_err();
- }),
- build_action_listener(|editor, action, cx| {
- editor.select_previous(action, cx).log_err();
- }),
- build_action_listener(Editor::toggle_comments),
- build_action_listener(Editor::select_larger_syntax_node),
- build_action_listener(Editor::select_smaller_syntax_node),
- build_action_listener(Editor::move_to_enclosing_bracket),
- build_action_listener(Editor::undo_selection),
- build_action_listener(Editor::redo_selection),
- build_action_listener(Editor::go_to_diagnostic),
- build_action_listener(Editor::go_to_prev_diagnostic),
- build_action_listener(Editor::go_to_hunk),
- build_action_listener(Editor::go_to_prev_hunk),
- build_action_listener(Editor::go_to_definition),
- build_action_listener(Editor::go_to_definition_split),
- build_action_listener(Editor::go_to_type_definition),
- build_action_listener(Editor::go_to_type_definition_split),
- build_action_listener(Editor::fold),
- build_action_listener(Editor::fold_at),
- build_action_listener(Editor::unfold_lines),
- build_action_listener(Editor::unfold_at),
- build_action_listener(Editor::fold_selected_ranges),
- build_action_listener(Editor::show_completions),
- build_action_listener(Editor::toggle_code_actions),
- // build_action_listener(Editor::open_excerpts), todo!()
- build_action_listener(Editor::toggle_soft_wrap),
- build_action_listener(Editor::toggle_inlay_hints),
- build_action_listener(Editor::reveal_in_finder),
- build_action_listener(Editor::copy_path),
- build_action_listener(Editor::copy_relative_path),
- build_action_listener(Editor::copy_highlight_json),
- build_action_listener(|editor, action, cx| {
- editor
- .format(action, cx)
- .map(|task| task.detach_and_log_err(cx));
- }),
- build_action_listener(Editor::restart_language_server),
- build_action_listener(Editor::show_character_palette),
- // build_action_listener(Editor::confirm_completion), todo!()
- build_action_listener(|editor, action, cx| {
- editor
- .confirm_code_action(action, cx)
- .map(|task| task.detach_and_log_err(cx));
- }),
- // build_action_listener(Editor::rename), todo!()
- // build_action_listener(Editor::confirm_rename), todo!()
- // build_action_listener(Editor::find_all_references), todo!()
- build_action_listener(Editor::next_copilot_suggestion),
- build_action_listener(Editor::previous_copilot_suggestion),
- build_action_listener(Editor::copilot_suggest),
- build_action_listener(Editor::context_menu_first),
- build_action_listener(Editor::context_menu_prev),
- build_action_listener(Editor::context_menu_next),
- build_action_listener(Editor::context_menu_last),
- build_key_listener(
- move |editor, key_down: &KeyDownEvent, dispatch_context, phase, cx| {
- if phase == DispatchPhase::Bubble {
- if let KeyMatch::Some(action) = cx.match_keystroke(
- &global_element_id,
- &key_down.keystroke,
- dispatch_context,
- ) {
- return Some(action);
- }
- }
-
- None
- },
- ),
- ]
-}
-
-fn build_key_listener<T: 'static>(
- listener: impl Fn(
- &mut Editor,
- &T,
- &[&DispatchContext],
- DispatchPhase,
- &mut ViewContext<Editor>,
- ) -> Option<Box<dyn Action>>
- + 'static,
-) -> (TypeId, KeyListener<Editor>) {
- (
- TypeId::of::<T>(),
- Box::new(move |editor, event, dispatch_context, phase, cx| {
- let key_event = event.downcast_ref::<T>()?;
- listener(editor, key_event, dispatch_context, phase, cx)
- }),
- )
-}
-
-fn build_action_listener<T: Action>(
+fn register_action<T: Action>(
+ cx: &mut ViewContext<Editor>,
listener: impl Fn(&mut Editor, &T, &mut ViewContext<Editor>) + 'static,
-) -> (TypeId, KeyListener<Editor>) {
- build_key_listener(move |editor, action: &T, dispatch_context, phase, cx| {
+) {
+ cx.on_action(TypeId::of::<T>(), move |editor, action, phase, cx| {
+ let action = action.downcast_ref().unwrap();
if phase == DispatchPhase::Bubble {
listener(editor, action, cx);
}
- None
})
}
@@ -30,6 +30,7 @@ use std::{
};
use text::Selection;
use theme::{ActiveTheme, Theme};
+use ui::{Label, LabelColor};
use util::{paths::PathExt, ResultExt, TryFutureExt};
use workspace::item::{BreadcrumbText, FollowEvent, FollowableEvents, FollowableItemHandle};
use workspace::{
@@ -595,16 +596,19 @@ impl Item for Editor {
.flex_row()
.items_center()
.gap_2()
- .child(self.title(cx).to_string())
+ .child(Label::new(self.title(cx).to_string()))
.children(detail.and_then(|detail| {
let path = path_for_buffer(&self.buffer, detail, false, cx)?;
let description = path.to_string_lossy();
Some(
- div()
- .text_color(theme.colors().text_muted)
- .text_xs()
- .child(util::truncate_and_trailoff(&description, MAX_TAB_TITLE_LEN)),
+ div().child(
+ Label::new(util::truncate_and_trailoff(
+ &description,
+ MAX_TAB_TITLE_LEN,
+ ))
+ .color(LabelColor::Muted),
+ ),
)
})),
)
@@ -1,8 +1,7 @@
use editor::{display_map::ToDisplayPoint, scroll::autoscroll::Autoscroll, Editor};
use gpui::{
actions, div, AppContext, Div, EventEmitter, ParentElement, Render, SharedString,
- StatefulInteractivity, StatelessInteractive, Styled, Subscription, View, ViewContext,
- VisualContext, WindowContext,
+ StatelessInteractive, Styled, Subscription, View, ViewContext, VisualContext, WindowContext,
};
use text::{Bias, Point};
use theme::ActiveTheme;
@@ -146,11 +145,11 @@ impl GoToLine {
}
impl Render for GoToLine {
- type Element = Div<Self, StatefulInteractivity<Self>>;
+ type Element = Div<Self>;
fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
modal(cx)
- .id("go to line")
+ .context("GoToLine")
.on_action(Self::cancel)
.on_action(Self::confirm)
.w_96()
@@ -0,0 +1 @@
+
@@ -1,6 +1,6 @@
use crate::SharedString;
use anyhow::{anyhow, Context, Result};
-use collections::{HashMap, HashSet};
+use collections::HashMap;
use lazy_static::lazy_static;
use parking_lot::{MappedRwLockReadGuard, RwLock, RwLockReadGuard};
use serde::Deserialize;
@@ -186,401 +186,3 @@ macro_rules! actions {
actions!($($rest)*);
};
}
-
-#[derive(Clone, Debug, Default, Eq, PartialEq)]
-pub struct DispatchContext {
- set: HashSet<SharedString>,
- map: HashMap<SharedString, SharedString>,
-}
-
-impl<'a> TryFrom<&'a str> for DispatchContext {
- type Error = anyhow::Error;
-
- fn try_from(value: &'a str) -> Result<Self> {
- Self::parse(value)
- }
-}
-
-impl DispatchContext {
- pub fn parse(source: &str) -> Result<Self> {
- let mut context = Self::default();
- let source = skip_whitespace(source);
- Self::parse_expr(&source, &mut context)?;
- Ok(context)
- }
-
- fn parse_expr(mut source: &str, context: &mut Self) -> Result<()> {
- if source.is_empty() {
- return Ok(());
- }
-
- let key = source
- .chars()
- .take_while(|c| is_identifier_char(*c))
- .collect::<String>();
- source = skip_whitespace(&source[key.len()..]);
- if let Some(suffix) = source.strip_prefix('=') {
- source = skip_whitespace(suffix);
- let value = source
- .chars()
- .take_while(|c| is_identifier_char(*c))
- .collect::<String>();
- source = skip_whitespace(&source[value.len()..]);
- context.set(key, value);
- } else {
- context.insert(key);
- }
-
- Self::parse_expr(source, context)
- }
-
- pub fn is_empty(&self) -> bool {
- self.set.is_empty() && self.map.is_empty()
- }
-
- pub fn clear(&mut self) {
- self.set.clear();
- self.map.clear();
- }
-
- pub fn extend(&mut self, other: &Self) {
- for v in &other.set {
- self.set.insert(v.clone());
- }
- for (k, v) in &other.map {
- self.map.insert(k.clone(), v.clone());
- }
- }
-
- pub fn insert<I: Into<SharedString>>(&mut self, identifier: I) {
- self.set.insert(identifier.into());
- }
-
- pub fn set<S1: Into<SharedString>, S2: Into<SharedString>>(&mut self, key: S1, value: S2) {
- self.map.insert(key.into(), value.into());
- }
-}
-
-#[derive(Clone, Debug, Eq, PartialEq, Hash)]
-pub enum DispatchContextPredicate {
- Identifier(SharedString),
- Equal(SharedString, SharedString),
- NotEqual(SharedString, SharedString),
- Child(Box<DispatchContextPredicate>, Box<DispatchContextPredicate>),
- Not(Box<DispatchContextPredicate>),
- And(Box<DispatchContextPredicate>, Box<DispatchContextPredicate>),
- Or(Box<DispatchContextPredicate>, Box<DispatchContextPredicate>),
-}
-
-impl DispatchContextPredicate {
- pub fn parse(source: &str) -> Result<Self> {
- let source = skip_whitespace(source);
- let (predicate, rest) = Self::parse_expr(source, 0)?;
- if let Some(next) = rest.chars().next() {
- Err(anyhow!("unexpected character {next:?}"))
- } else {
- Ok(predicate)
- }
- }
-
- pub fn eval(&self, contexts: &[&DispatchContext]) -> bool {
- let Some(context) = contexts.last() else {
- return false;
- };
- match self {
- Self::Identifier(name) => context.set.contains(name),
- Self::Equal(left, right) => context
- .map
- .get(left)
- .map(|value| value == right)
- .unwrap_or(false),
- Self::NotEqual(left, right) => context
- .map
- .get(left)
- .map(|value| value != right)
- .unwrap_or(true),
- Self::Not(pred) => !pred.eval(contexts),
- Self::Child(parent, child) => {
- parent.eval(&contexts[..contexts.len() - 1]) && child.eval(contexts)
- }
- Self::And(left, right) => left.eval(contexts) && right.eval(contexts),
- Self::Or(left, right) => left.eval(contexts) || right.eval(contexts),
- }
- }
-
- fn parse_expr(mut source: &str, min_precedence: u32) -> anyhow::Result<(Self, &str)> {
- type Op = fn(
- DispatchContextPredicate,
- DispatchContextPredicate,
- ) -> Result<DispatchContextPredicate>;
-
- let (mut predicate, rest) = Self::parse_primary(source)?;
- source = rest;
-
- 'parse: loop {
- for (operator, precedence, constructor) in [
- (">", PRECEDENCE_CHILD, Self::new_child as Op),
- ("&&", PRECEDENCE_AND, Self::new_and as Op),
- ("||", PRECEDENCE_OR, Self::new_or as Op),
- ("==", PRECEDENCE_EQ, Self::new_eq as Op),
- ("!=", PRECEDENCE_EQ, Self::new_neq as Op),
- ] {
- if source.starts_with(operator) && precedence >= min_precedence {
- source = skip_whitespace(&source[operator.len()..]);
- let (right, rest) = Self::parse_expr(source, precedence + 1)?;
- predicate = constructor(predicate, right)?;
- source = rest;
- continue 'parse;
- }
- }
- break;
- }
-
- Ok((predicate, source))
- }
-
- fn parse_primary(mut source: &str) -> anyhow::Result<(Self, &str)> {
- let next = source
- .chars()
- .next()
- .ok_or_else(|| anyhow!("unexpected eof"))?;
- match next {
- '(' => {
- source = skip_whitespace(&source[1..]);
- let (predicate, rest) = Self::parse_expr(source, 0)?;
- if rest.starts_with(')') {
- source = skip_whitespace(&rest[1..]);
- Ok((predicate, source))
- } else {
- Err(anyhow!("expected a ')'"))
- }
- }
- '!' => {
- let source = skip_whitespace(&source[1..]);
- let (predicate, source) = Self::parse_expr(&source, PRECEDENCE_NOT)?;
- Ok((DispatchContextPredicate::Not(Box::new(predicate)), source))
- }
- _ if is_identifier_char(next) => {
- let len = source
- .find(|c: char| !is_identifier_char(c))
- .unwrap_or(source.len());
- let (identifier, rest) = source.split_at(len);
- source = skip_whitespace(rest);
- Ok((
- DispatchContextPredicate::Identifier(identifier.to_string().into()),
- source,
- ))
- }
- _ => Err(anyhow!("unexpected character {next:?}")),
- }
- }
-
- fn new_or(self, other: Self) -> Result<Self> {
- Ok(Self::Or(Box::new(self), Box::new(other)))
- }
-
- fn new_and(self, other: Self) -> Result<Self> {
- Ok(Self::And(Box::new(self), Box::new(other)))
- }
-
- fn new_child(self, other: Self) -> Result<Self> {
- Ok(Self::Child(Box::new(self), Box::new(other)))
- }
-
- fn new_eq(self, other: Self) -> Result<Self> {
- if let (Self::Identifier(left), Self::Identifier(right)) = (self, other) {
- Ok(Self::Equal(left, right))
- } else {
- Err(anyhow!("operands must be identifiers"))
- }
- }
-
- fn new_neq(self, other: Self) -> Result<Self> {
- if let (Self::Identifier(left), Self::Identifier(right)) = (self, other) {
- Ok(Self::NotEqual(left, right))
- } else {
- Err(anyhow!("operands must be identifiers"))
- }
- }
-}
-
-const PRECEDENCE_CHILD: u32 = 1;
-const PRECEDENCE_OR: u32 = 2;
-const PRECEDENCE_AND: u32 = 3;
-const PRECEDENCE_EQ: u32 = 4;
-const PRECEDENCE_NOT: u32 = 5;
-
-fn is_identifier_char(c: char) -> bool {
- c.is_alphanumeric() || c == '_' || c == '-'
-}
-
-fn skip_whitespace(source: &str) -> &str {
- let len = source
- .find(|c: char| !c.is_whitespace())
- .unwrap_or(source.len());
- &source[len..]
-}
-
-#[cfg(test)]
-mod tests {
- use super::*;
- use crate as gpui;
- use DispatchContextPredicate::*;
-
- #[test]
- fn test_actions_definition() {
- {
- actions!(A, B, C, D, E, F, G);
- }
-
- {
- actions!(
- A,
- B,
- C,
- D,
- E,
- F,
- G, // Don't wrap, test the trailing comma
- );
- }
- }
-
- #[test]
- fn test_parse_context() {
- let mut expected = DispatchContext::default();
- expected.set("foo", "bar");
- expected.insert("baz");
- assert_eq!(DispatchContext::parse("baz foo=bar").unwrap(), expected);
- assert_eq!(DispatchContext::parse("foo = bar baz").unwrap(), expected);
- assert_eq!(
- DispatchContext::parse(" baz foo = bar baz").unwrap(),
- expected
- );
- assert_eq!(DispatchContext::parse(" foo = bar baz").unwrap(), expected);
- }
-
- #[test]
- fn test_parse_identifiers() {
- // Identifiers
- assert_eq!(
- DispatchContextPredicate::parse("abc12").unwrap(),
- Identifier("abc12".into())
- );
- assert_eq!(
- DispatchContextPredicate::parse("_1a").unwrap(),
- Identifier("_1a".into())
- );
- }
-
- #[test]
- fn test_parse_negations() {
- assert_eq!(
- DispatchContextPredicate::parse("!abc").unwrap(),
- Not(Box::new(Identifier("abc".into())))
- );
- assert_eq!(
- DispatchContextPredicate::parse(" ! ! abc").unwrap(),
- Not(Box::new(Not(Box::new(Identifier("abc".into())))))
- );
- }
-
- #[test]
- fn test_parse_equality_operators() {
- assert_eq!(
- DispatchContextPredicate::parse("a == b").unwrap(),
- Equal("a".into(), "b".into())
- );
- assert_eq!(
- DispatchContextPredicate::parse("c!=d").unwrap(),
- NotEqual("c".into(), "d".into())
- );
- assert_eq!(
- DispatchContextPredicate::parse("c == !d")
- .unwrap_err()
- .to_string(),
- "operands must be identifiers"
- );
- }
-
- #[test]
- fn test_parse_boolean_operators() {
- assert_eq!(
- DispatchContextPredicate::parse("a || b").unwrap(),
- Or(
- Box::new(Identifier("a".into())),
- Box::new(Identifier("b".into()))
- )
- );
- assert_eq!(
- DispatchContextPredicate::parse("a || !b && c").unwrap(),
- Or(
- Box::new(Identifier("a".into())),
- Box::new(And(
- Box::new(Not(Box::new(Identifier("b".into())))),
- Box::new(Identifier("c".into()))
- ))
- )
- );
- assert_eq!(
- DispatchContextPredicate::parse("a && b || c&&d").unwrap(),
- Or(
- Box::new(And(
- Box::new(Identifier("a".into())),
- Box::new(Identifier("b".into()))
- )),
- Box::new(And(
- Box::new(Identifier("c".into())),
- Box::new(Identifier("d".into()))
- ))
- )
- );
- assert_eq!(
- DispatchContextPredicate::parse("a == b && c || d == e && f").unwrap(),
- Or(
- Box::new(And(
- Box::new(Equal("a".into(), "b".into())),
- Box::new(Identifier("c".into()))
- )),
- Box::new(And(
- Box::new(Equal("d".into(), "e".into())),
- Box::new(Identifier("f".into()))
- ))
- )
- );
- assert_eq!(
- DispatchContextPredicate::parse("a && b && c && d").unwrap(),
- And(
- Box::new(And(
- Box::new(And(
- Box::new(Identifier("a".into())),
- Box::new(Identifier("b".into()))
- )),
- Box::new(Identifier("c".into())),
- )),
- Box::new(Identifier("d".into()))
- ),
- );
- }
-
- #[test]
- fn test_parse_parenthesized_expressions() {
- assert_eq!(
- DispatchContextPredicate::parse("a && (b == c || d != e)").unwrap(),
- And(
- Box::new(Identifier("a".into())),
- Box::new(Or(
- Box::new(Equal("b".into(), "c".into())),
- Box::new(NotEqual("d".into(), "e".into())),
- )),
- ),
- );
- assert_eq!(
- DispatchContextPredicate::parse(" ( a || b ) ").unwrap(),
- Or(
- Box::new(Identifier("a".into())),
- Box::new(Identifier("b".into())),
- )
- );
- }
-}
@@ -641,14 +641,19 @@ impl AppContext {
// The window might change focus multiple times in an effect cycle.
// We only honor effects for the most recently focused handle.
if cx.window.focus == focused {
+ // if someone calls focus multiple times in one frame with the same handle
+ // the first apply_focus_changed_effect will have taken the last blur already
+ // and run the rest of this, so we can return.
+ let Some(last_blur) = cx.window.last_blur.take() else {
+ return;
+ };
+
let focused = focused
.map(|id| FocusHandle::for_id(id, &cx.window.focus_handles).unwrap());
- let blurred = cx
- .window
- .last_blur
- .take()
- .unwrap()
- .and_then(|id| FocusHandle::for_id(id, &cx.window.focus_handles));
+
+ let blurred =
+ last_blur.and_then(|id| FocusHandle::for_id(id, &cx.window.focus_handles));
+
let focus_changed = focused.is_some() || blurred.is_some();
let event = FocusEvent { focused, blurred };
@@ -1,29 +1,33 @@
+use std::fmt::Debug;
+
use crate::{
- point, AnyElement, BorrowWindow, Bounds, Component, Element, ElementFocus, ElementId,
- ElementInteractivity, FocusDisabled, FocusEnabled, FocusHandle, FocusListeners, Focusable,
- GlobalElementId, GroupBounds, InteractiveElementState, LayoutId, Overflow, ParentElement,
- Pixels, Point, SharedString, StatefulInteractive, StatefulInteractivity, StatelessInteractive,
- StatelessInteractivity, Style, StyleRefinement, Styled, ViewContext, Visibility,
+ point, AnyElement, BorrowWindow, Bounds, Component, Element, ElementId, ElementInteractivity,
+ FocusHandle, FocusListeners, Focusable, FocusableKeyDispatch, GlobalElementId, GroupBounds,
+ InteractiveElementState, KeyContext, KeyDispatch, LayoutId, NonFocusableKeyDispatch, Overflow,
+ ParentElement, Pixels, Point, SharedString, StatefulInteractive, StatefulInteractivity,
+ StatelessInteractive, StatelessInteractivity, Style, StyleRefinement, Styled, ViewContext,
+ Visibility,
};
use refineable::Refineable;
use smallvec::SmallVec;
+use util::ResultExt;
pub struct Div<
V: 'static,
I: ElementInteractivity<V> = StatelessInteractivity<V>,
- F: ElementFocus<V> = FocusDisabled,
+ K: KeyDispatch<V> = NonFocusableKeyDispatch,
> {
interactivity: I,
- focus: F,
+ key_dispatch: K,
children: SmallVec<[AnyElement<V>; 2]>,
group: Option<SharedString>,
base_style: StyleRefinement,
}
-pub fn div<V: 'static>() -> Div<V, StatelessInteractivity<V>, FocusDisabled> {
+pub fn div<V: 'static>() -> Div<V, StatelessInteractivity<V>, NonFocusableKeyDispatch> {
Div {
interactivity: StatelessInteractivity::default(),
- focus: FocusDisabled,
+ key_dispatch: NonFocusableKeyDispatch::default(),
children: SmallVec::new(),
group: None,
base_style: StyleRefinement::default(),
@@ -33,12 +37,12 @@ pub fn div<V: 'static>() -> Div<V, StatelessInteractivity<V>, FocusDisabled> {
impl<V, F> Div<V, StatelessInteractivity<V>, F>
where
V: 'static,
- F: ElementFocus<V>,
+ F: KeyDispatch<V>,
{
pub fn id(self, id: impl Into<ElementId>) -> Div<V, StatefulInteractivity<V>, F> {
Div {
interactivity: StatefulInteractivity::new(id.into(), self.interactivity),
- focus: self.focus,
+ key_dispatch: self.key_dispatch,
children: self.children,
group: self.group,
base_style: self.base_style,
@@ -49,7 +53,7 @@ where
impl<V, I, F> Div<V, I, F>
where
I: ElementInteractivity<V>,
- F: ElementFocus<V>,
+ F: KeyDispatch<V>,
{
pub fn group(mut self, group: impl Into<SharedString>) -> Self {
self.group = Some(group.into());
@@ -61,6 +65,18 @@ where
self
}
+ pub fn context<C>(mut self, context: C) -> Self
+ where
+ Self: Sized,
+ C: TryInto<KeyContext>,
+ C::Error: Debug,
+ {
+ if let Some(context) = context.try_into().log_err() {
+ *self.key_dispatch.key_context_mut() = context;
+ }
+ self
+ }
+
pub fn overflow_hidden(mut self) -> Self {
self.base_style.overflow.x = Some(Overflow::Hidden);
self.base_style.overflow.y = Some(Overflow::Hidden);
@@ -97,7 +113,7 @@ where
) -> Style {
let mut computed_style = Style::default();
computed_style.refine(&self.base_style);
- self.focus.refine_style(&mut computed_style, cx);
+ self.key_dispatch.refine_style(&mut computed_style, cx);
self.interactivity.refine_style(
&mut computed_style,
bounds,
@@ -108,11 +124,11 @@ where
}
}
-impl<V: 'static> Div<V, StatefulInteractivity<V>, FocusDisabled> {
- pub fn focusable(self) -> Div<V, StatefulInteractivity<V>, FocusEnabled<V>> {
+impl<V: 'static> Div<V, StatefulInteractivity<V>, NonFocusableKeyDispatch> {
+ pub fn focusable(self) -> Div<V, StatefulInteractivity<V>, FocusableKeyDispatch<V>> {
Div {
interactivity: self.interactivity,
- focus: FocusEnabled::new(),
+ key_dispatch: FocusableKeyDispatch::new(self.key_dispatch),
children: self.children,
group: self.group,
base_style: self.base_style,
@@ -122,10 +138,10 @@ impl<V: 'static> Div<V, StatefulInteractivity<V>, FocusDisabled> {
pub fn track_focus(
self,
handle: &FocusHandle,
- ) -> Div<V, StatefulInteractivity<V>, FocusEnabled<V>> {
+ ) -> Div<V, StatefulInteractivity<V>, FocusableKeyDispatch<V>> {
Div {
interactivity: self.interactivity,
- focus: FocusEnabled::tracked(handle),
+ key_dispatch: FocusableKeyDispatch::tracked(self.key_dispatch, handle),
children: self.children,
group: self.group,
base_style: self.base_style,
@@ -149,14 +165,14 @@ impl<V: 'static> Div<V, StatefulInteractivity<V>, FocusDisabled> {
}
}
-impl<V: 'static> Div<V, StatelessInteractivity<V>, FocusDisabled> {
+impl<V: 'static> Div<V, StatelessInteractivity<V>, NonFocusableKeyDispatch> {
pub fn track_focus(
self,
handle: &FocusHandle,
- ) -> Div<V, StatefulInteractivity<V>, FocusEnabled<V>> {
+ ) -> Div<V, StatefulInteractivity<V>, FocusableKeyDispatch<V>> {
Div {
interactivity: self.interactivity.into_stateful(handle),
- focus: handle.clone().into(),
+ key_dispatch: FocusableKeyDispatch::tracked(self.key_dispatch, handle),
children: self.children,
group: self.group,
base_style: self.base_style,
@@ -164,25 +180,25 @@ impl<V: 'static> Div<V, StatelessInteractivity<V>, FocusDisabled> {
}
}
-impl<V, I> Focusable<V> for Div<V, I, FocusEnabled<V>>
+impl<V, I> Focusable<V> for Div<V, I, FocusableKeyDispatch<V>>
where
V: 'static,
I: ElementInteractivity<V>,
{
fn focus_listeners(&mut self) -> &mut FocusListeners<V> {
- &mut self.focus.focus_listeners
+ &mut self.key_dispatch.focus_listeners
}
fn set_focus_style(&mut self, style: StyleRefinement) {
- self.focus.focus_style = style;
+ self.key_dispatch.focus_style = style;
}
fn set_focus_in_style(&mut self, style: StyleRefinement) {
- self.focus.focus_in_style = style;
+ self.key_dispatch.focus_in_style = style;
}
fn set_in_focus_style(&mut self, style: StyleRefinement) {
- self.focus.in_focus_style = style;
+ self.key_dispatch.in_focus_style = style;
}
}
@@ -196,7 +212,7 @@ pub struct DivState {
impl<V, I, F> Element<V> for Div<V, I, F>
where
I: ElementInteractivity<V>,
- F: ElementFocus<V>,
+ F: KeyDispatch<V>,
{
type ElementState = DivState;
@@ -213,14 +229,18 @@ where
cx: &mut ViewContext<V>,
) -> Self::ElementState {
let mut element_state = element_state.unwrap_or_default();
- self.interactivity.initialize(cx, |cx| {
- self.focus
- .initialize(element_state.focus_handle.take(), cx, |focus_handle, cx| {
+ self.with_element_id(cx, |this, _global_id, cx| {
+ this.key_dispatch.initialize(
+ element_state.focus_handle.take(),
+ cx,
+ |focus_handle, cx| {
+ this.interactivity.initialize(cx);
element_state.focus_handle = focus_handle;
- for child in &mut self.children {
+ for child in &mut this.children {
child.initialize(view_state, cx);
}
- })
+ },
+ );
});
element_state
}
@@ -288,7 +308,7 @@ where
cx.with_z_index(z_index, |cx| {
cx.with_z_index(0, |cx| {
style.paint(bounds, cx);
- this.focus.paint(bounds, cx);
+ this.key_dispatch.paint(bounds, cx);
this.interactivity.paint(
bounds,
content_size,
@@ -321,7 +341,7 @@ where
impl<V, I, F> Component<V> for Div<V, I, F>
where
I: ElementInteractivity<V>,
- F: ElementFocus<V>,
+ F: KeyDispatch<V>,
{
fn render(self) -> AnyElement<V> {
AnyElement::new(self)
@@ -331,7 +351,7 @@ where
impl<V, I, F> ParentElement<V> for Div<V, I, F>
where
I: ElementInteractivity<V>,
- F: ElementFocus<V>,
+ F: KeyDispatch<V>,
{
fn children_mut(&mut self) -> &mut SmallVec<[AnyElement<V>; 2]> {
&mut self.children
@@ -341,7 +361,7 @@ where
impl<V, I, F> Styled for Div<V, I, F>
where
I: ElementInteractivity<V>,
- F: ElementFocus<V>,
+ F: KeyDispatch<V>,
{
fn style(&mut self) -> &mut StyleRefinement {
&mut self.base_style
@@ -351,7 +371,7 @@ where
impl<V, I, F> StatelessInteractive<V> for Div<V, I, F>
where
I: ElementInteractivity<V>,
- F: ElementFocus<V>,
+ F: KeyDispatch<V>,
{
fn stateless_interactivity(&mut self) -> &mut StatelessInteractivity<V> {
self.interactivity.as_stateless_mut()
@@ -360,7 +380,7 @@ where
impl<V, F> StatefulInteractive<V> for Div<V, StatefulInteractivity<V>, F>
where
- F: ElementFocus<V>,
+ F: KeyDispatch<V>,
{
fn stateful_interactivity(&mut self) -> &mut StatefulInteractivity<V> {
&mut self.interactivity
@@ -1,7 +1,7 @@
use crate::{
- div, AnyElement, BorrowWindow, Bounds, Component, Div, DivState, Element, ElementFocus,
- ElementId, ElementInteractivity, FocusDisabled, FocusEnabled, FocusListeners, Focusable,
- LayoutId, Pixels, SharedString, StatefulInteractive, StatefulInteractivity,
+ div, AnyElement, BorrowWindow, Bounds, Component, Div, DivState, Element, ElementId,
+ ElementInteractivity, FocusListeners, Focusable, FocusableKeyDispatch, KeyDispatch, LayoutId,
+ NonFocusableKeyDispatch, Pixels, SharedString, StatefulInteractive, StatefulInteractivity,
StatelessInteractive, StatelessInteractivity, StyleRefinement, Styled, ViewContext,
};
use futures::FutureExt;
@@ -10,14 +10,14 @@ use util::ResultExt;
pub struct Img<
V: 'static,
I: ElementInteractivity<V> = StatelessInteractivity<V>,
- F: ElementFocus<V> = FocusDisabled,
+ F: KeyDispatch<V> = NonFocusableKeyDispatch,
> {
base: Div<V, I, F>,
uri: Option<SharedString>,
grayscale: bool,
}
-pub fn img<V: 'static>() -> Img<V, StatelessInteractivity<V>, FocusDisabled> {
+pub fn img<V: 'static>() -> Img<V, StatelessInteractivity<V>, NonFocusableKeyDispatch> {
Img {
base: div(),
uri: None,
@@ -29,7 +29,7 @@ impl<V, I, F> Img<V, I, F>
where
V: 'static,
I: ElementInteractivity<V>,
- F: ElementFocus<V>,
+ F: KeyDispatch<V>,
{
pub fn uri(mut self, uri: impl Into<SharedString>) -> Self {
self.uri = Some(uri.into());
@@ -44,7 +44,7 @@ where
impl<V, F> Img<V, StatelessInteractivity<V>, F>
where
- F: ElementFocus<V>,
+ F: KeyDispatch<V>,
{
pub fn id(self, id: impl Into<ElementId>) -> Img<V, StatefulInteractivity<V>, F> {
Img {
@@ -58,7 +58,7 @@ where
impl<V, I, F> Component<V> for Img<V, I, F>
where
I: ElementInteractivity<V>,
- F: ElementFocus<V>,
+ F: KeyDispatch<V>,
{
fn render(self) -> AnyElement<V> {
AnyElement::new(self)
@@ -68,7 +68,7 @@ where
impl<V, I, F> Element<V> for Img<V, I, F>
where
I: ElementInteractivity<V>,
- F: ElementFocus<V>,
+ F: KeyDispatch<V>,
{
type ElementState = DivState;
@@ -137,7 +137,7 @@ where
impl<V, I, F> Styled for Img<V, I, F>
where
I: ElementInteractivity<V>,
- F: ElementFocus<V>,
+ F: KeyDispatch<V>,
{
fn style(&mut self) -> &mut StyleRefinement {
self.base.style()
@@ -147,7 +147,7 @@ where
impl<V, I, F> StatelessInteractive<V> for Img<V, I, F>
where
I: ElementInteractivity<V>,
- F: ElementFocus<V>,
+ F: KeyDispatch<V>,
{
fn stateless_interactivity(&mut self) -> &mut StatelessInteractivity<V> {
self.base.stateless_interactivity()
@@ -156,14 +156,14 @@ where
impl<V, F> StatefulInteractive<V> for Img<V, StatefulInteractivity<V>, F>
where
- F: ElementFocus<V>,
+ F: KeyDispatch<V>,
{
fn stateful_interactivity(&mut self) -> &mut StatefulInteractivity<V> {
self.base.stateful_interactivity()
}
}
-impl<V, I> Focusable<V> for Img<V, I, FocusEnabled<V>>
+impl<V, I> Focusable<V> for Img<V, I, FocusableKeyDispatch<V>>
where
V: 'static,
I: ElementInteractivity<V>,
@@ -1,21 +1,21 @@
use crate::{
- div, AnyElement, Bounds, Component, Div, DivState, Element, ElementFocus, ElementId,
- ElementInteractivity, FocusDisabled, FocusEnabled, FocusListeners, Focusable, LayoutId, Pixels,
- SharedString, StatefulInteractive, StatefulInteractivity, StatelessInteractive,
- StatelessInteractivity, StyleRefinement, Styled, ViewContext,
+ div, AnyElement, Bounds, Component, Div, DivState, Element, ElementId, ElementInteractivity,
+ FocusListeners, Focusable, FocusableKeyDispatch, KeyDispatch, LayoutId,
+ NonFocusableKeyDispatch, Pixels, SharedString, StatefulInteractive, StatefulInteractivity,
+ StatelessInteractive, StatelessInteractivity, StyleRefinement, Styled, ViewContext,
};
use util::ResultExt;
pub struct Svg<
V: 'static,
I: ElementInteractivity<V> = StatelessInteractivity<V>,
- F: ElementFocus<V> = FocusDisabled,
+ F: KeyDispatch<V> = NonFocusableKeyDispatch,
> {
base: Div<V, I, F>,
path: Option<SharedString>,
}
-pub fn svg<V: 'static>() -> Svg<V, StatelessInteractivity<V>, FocusDisabled> {
+pub fn svg<V: 'static>() -> Svg<V, StatelessInteractivity<V>, NonFocusableKeyDispatch> {
Svg {
base: div(),
path: None,
@@ -25,7 +25,7 @@ pub fn svg<V: 'static>() -> Svg<V, StatelessInteractivity<V>, FocusDisabled> {
impl<V, I, F> Svg<V, I, F>
where
I: ElementInteractivity<V>,
- F: ElementFocus<V>,
+ F: KeyDispatch<V>,
{
pub fn path(mut self, path: impl Into<SharedString>) -> Self {
self.path = Some(path.into());
@@ -35,7 +35,7 @@ where
impl<V, F> Svg<V, StatelessInteractivity<V>, F>
where
- F: ElementFocus<V>,
+ F: KeyDispatch<V>,
{
pub fn id(self, id: impl Into<ElementId>) -> Svg<V, StatefulInteractivity<V>, F> {
Svg {
@@ -48,7 +48,7 @@ where
impl<V, I, F> Component<V> for Svg<V, I, F>
where
I: ElementInteractivity<V>,
- F: ElementFocus<V>,
+ F: KeyDispatch<V>,
{
fn render(self) -> AnyElement<V> {
AnyElement::new(self)
@@ -58,7 +58,7 @@ where
impl<V, I, F> Element<V> for Svg<V, I, F>
where
I: ElementInteractivity<V>,
- F: ElementFocus<V>,
+ F: KeyDispatch<V>,
{
type ElementState = DivState;
@@ -108,7 +108,7 @@ where
impl<V, I, F> Styled for Svg<V, I, F>
where
I: ElementInteractivity<V>,
- F: ElementFocus<V>,
+ F: KeyDispatch<V>,
{
fn style(&mut self) -> &mut StyleRefinement {
self.base.style()
@@ -118,7 +118,7 @@ where
impl<V, I, F> StatelessInteractive<V> for Svg<V, I, F>
where
I: ElementInteractivity<V>,
- F: ElementFocus<V>,
+ F: KeyDispatch<V>,
{
fn stateless_interactivity(&mut self) -> &mut StatelessInteractivity<V> {
self.base.stateless_interactivity()
@@ -128,14 +128,14 @@ where
impl<V, F> StatefulInteractive<V> for Svg<V, StatefulInteractivity<V>, F>
where
V: 'static,
- F: ElementFocus<V>,
+ F: KeyDispatch<V>,
{
fn stateful_interactivity(&mut self) -> &mut StatefulInteractivity<V> {
self.base.stateful_interactivity()
}
}
-impl<V: 'static, I> Focusable<V> for Svg<V, I, FocusEnabled<V>>
+impl<V: 'static, I> Focusable<V> for Svg<V, I, FocusableKeyDispatch<V>>
where
I: ElementInteractivity<V>,
{
@@ -1,252 +0,0 @@
-use crate::{
- Bounds, DispatchPhase, Element, FocusEvent, FocusHandle, MouseDownEvent, Pixels, Style,
- StyleRefinement, ViewContext, WindowContext,
-};
-use refineable::Refineable;
-use smallvec::SmallVec;
-
-pub type FocusListeners<V> = SmallVec<[FocusListener<V>; 2]>;
-
-pub type FocusListener<V> =
- Box<dyn Fn(&mut V, &FocusHandle, &FocusEvent, &mut ViewContext<V>) + 'static>;
-
-pub trait Focusable<V: 'static>: Element<V> {
- fn focus_listeners(&mut self) -> &mut FocusListeners<V>;
- fn set_focus_style(&mut self, style: StyleRefinement);
- fn set_focus_in_style(&mut self, style: StyleRefinement);
- fn set_in_focus_style(&mut self, style: StyleRefinement);
-
- fn focus(mut self, f: impl FnOnce(StyleRefinement) -> StyleRefinement) -> Self
- where
- Self: Sized,
- {
- self.set_focus_style(f(StyleRefinement::default()));
- self
- }
-
- fn focus_in(mut self, f: impl FnOnce(StyleRefinement) -> StyleRefinement) -> Self
- where
- Self: Sized,
- {
- self.set_focus_in_style(f(StyleRefinement::default()));
- self
- }
-
- fn in_focus(mut self, f: impl FnOnce(StyleRefinement) -> StyleRefinement) -> Self
- where
- Self: Sized,
- {
- self.set_in_focus_style(f(StyleRefinement::default()));
- self
- }
-
- fn on_focus(
- mut self,
- listener: impl Fn(&mut V, &FocusEvent, &mut ViewContext<V>) + 'static,
- ) -> Self
- where
- Self: Sized,
- {
- self.focus_listeners()
- .push(Box::new(move |view, focus_handle, event, cx| {
- if event.focused.as_ref() == Some(focus_handle) {
- listener(view, event, cx)
- }
- }));
- self
- }
-
- fn on_blur(
- mut self,
- listener: impl Fn(&mut V, &FocusEvent, &mut ViewContext<V>) + 'static,
- ) -> Self
- where
- Self: Sized,
- {
- self.focus_listeners()
- .push(Box::new(move |view, focus_handle, event, cx| {
- if event.blurred.as_ref() == Some(focus_handle) {
- listener(view, event, cx)
- }
- }));
- self
- }
-
- fn on_focus_in(
- mut self,
- listener: impl Fn(&mut V, &FocusEvent, &mut ViewContext<V>) + 'static,
- ) -> Self
- where
- Self: Sized,
- {
- self.focus_listeners()
- .push(Box::new(move |view, focus_handle, event, cx| {
- let descendant_blurred = event
- .blurred
- .as_ref()
- .map_or(false, |blurred| focus_handle.contains(blurred, cx));
- let descendant_focused = event
- .focused
- .as_ref()
- .map_or(false, |focused| focus_handle.contains(focused, cx));
-
- if !descendant_blurred && descendant_focused {
- listener(view, event, cx)
- }
- }));
- self
- }
-
- fn on_focus_out(
- mut self,
- listener: impl Fn(&mut V, &FocusEvent, &mut ViewContext<V>) + 'static,
- ) -> Self
- where
- Self: Sized,
- {
- self.focus_listeners()
- .push(Box::new(move |view, focus_handle, event, cx| {
- let descendant_blurred = event
- .blurred
- .as_ref()
- .map_or(false, |blurred| focus_handle.contains(blurred, cx));
- let descendant_focused = event
- .focused
- .as_ref()
- .map_or(false, |focused| focus_handle.contains(focused, cx));
- if descendant_blurred && !descendant_focused {
- listener(view, event, cx)
- }
- }));
- self
- }
-}
-
-pub trait ElementFocus<V: 'static>: 'static {
- fn as_focusable(&self) -> Option<&FocusEnabled<V>>;
- fn as_focusable_mut(&mut self) -> Option<&mut FocusEnabled<V>>;
-
- fn initialize<R>(
- &mut self,
- focus_handle: Option<FocusHandle>,
- cx: &mut ViewContext<V>,
- f: impl FnOnce(Option<FocusHandle>, &mut ViewContext<V>) -> R,
- ) -> R {
- if let Some(focusable) = self.as_focusable_mut() {
- let focus_handle = focusable
- .focus_handle
- .get_or_insert_with(|| focus_handle.unwrap_or_else(|| cx.focus_handle()))
- .clone();
- for listener in focusable.focus_listeners.drain(..) {
- let focus_handle = focus_handle.clone();
- cx.on_focus_changed(move |view, event, cx| {
- listener(view, &focus_handle, event, cx)
- });
- }
- cx.with_focus(focus_handle.clone(), |cx| f(Some(focus_handle), cx))
- } else {
- f(None, cx)
- }
- }
-
- fn refine_style(&self, style: &mut Style, cx: &WindowContext) {
- if let Some(focusable) = self.as_focusable() {
- let focus_handle = focusable
- .focus_handle
- .as_ref()
- .expect("must call initialize before refine_style");
- if focus_handle.contains_focused(cx) {
- style.refine(&focusable.focus_in_style);
- }
-
- if focus_handle.within_focused(cx) {
- style.refine(&focusable.in_focus_style);
- }
-
- if focus_handle.is_focused(cx) {
- style.refine(&focusable.focus_style);
- }
- }
- }
-
- fn paint(&self, bounds: Bounds<Pixels>, cx: &mut WindowContext) {
- if let Some(focusable) = self.as_focusable() {
- let focus_handle = focusable
- .focus_handle
- .clone()
- .expect("must call initialize before paint");
- cx.on_mouse_event(move |event: &MouseDownEvent, phase, cx| {
- if phase == DispatchPhase::Bubble && bounds.contains_point(&event.position) {
- if !cx.default_prevented() {
- cx.focus(&focus_handle);
- cx.prevent_default();
- }
- }
- })
- }
- }
-}
-
-pub struct FocusEnabled<V> {
- pub focus_handle: Option<FocusHandle>,
- pub focus_listeners: FocusListeners<V>,
- pub focus_style: StyleRefinement,
- pub focus_in_style: StyleRefinement,
- pub in_focus_style: StyleRefinement,
-}
-
-impl<V> FocusEnabled<V> {
- pub fn new() -> Self {
- Self {
- focus_handle: None,
- focus_listeners: FocusListeners::default(),
- focus_style: StyleRefinement::default(),
- focus_in_style: StyleRefinement::default(),
- in_focus_style: StyleRefinement::default(),
- }
- }
-
- pub fn tracked(handle: &FocusHandle) -> Self {
- Self {
- focus_handle: Some(handle.clone()),
- focus_listeners: FocusListeners::default(),
- focus_style: StyleRefinement::default(),
- focus_in_style: StyleRefinement::default(),
- in_focus_style: StyleRefinement::default(),
- }
- }
-}
-
-impl<V: 'static> ElementFocus<V> for FocusEnabled<V> {
- fn as_focusable(&self) -> Option<&FocusEnabled<V>> {
- Some(self)
- }
-
- fn as_focusable_mut(&mut self) -> Option<&mut FocusEnabled<V>> {
- Some(self)
- }
-}
-
-impl<V> From<FocusHandle> for FocusEnabled<V> {
- fn from(value: FocusHandle) -> Self {
- Self {
- focus_handle: Some(value),
- focus_listeners: FocusListeners::default(),
- focus_style: StyleRefinement::default(),
- focus_in_style: StyleRefinement::default(),
- in_focus_style: StyleRefinement::default(),
- }
- }
-}
-
-pub struct FocusDisabled;
-
-impl<V: 'static> ElementFocus<V> for FocusDisabled {
- fn as_focusable(&self) -> Option<&FocusEnabled<V>> {
- None
- }
-
- fn as_focusable_mut(&mut self) -> Option<&mut FocusEnabled<V>> {
- None
- }
-}
@@ -6,13 +6,14 @@ mod color;
mod element;
mod elements;
mod executor;
-mod focusable;
mod geometry;
mod image_cache;
mod input;
mod interactive;
+mod key_dispatch;
mod keymap;
mod platform;
+pub mod prelude;
mod scene;
mod style;
mod styled;
@@ -41,12 +42,12 @@ pub use ctor::ctor;
pub use element::*;
pub use elements::*;
pub use executor::*;
-pub use focusable::*;
pub use geometry::*;
pub use gpui2_macros::*;
pub use image_cache::*;
pub use input::*;
pub use interactive::*;
+pub use key_dispatch::*;
pub use keymap::*;
pub use platform::*;
use private::Sealed;
@@ -1,8 +1,8 @@
use crate::{
- div, point, px, Action, AnyDrag, AnyTooltip, AnyView, AppContext, BorrowWindow, Bounds,
- Component, DispatchContext, DispatchPhase, Div, Element, ElementId, FocusHandle, KeyMatch,
- Keystroke, Modifiers, Overflow, Pixels, Point, Render, SharedString, Size, Style,
- StyleRefinement, Task, View, ViewContext,
+ div, point, px, Action, AnyDrag, AnyTooltip, AnyView, AppContext, Bounds, Component,
+ DispatchPhase, Div, Element, ElementId, FocusHandle, KeyContext, Keystroke, Modifiers,
+ Overflow, Pixels, Point, Render, SharedString, Size, Style, StyleRefinement, Task, View,
+ ViewContext,
};
use collections::HashMap;
use derive_more::{Deref, DerefMut};
@@ -164,55 +164,41 @@ pub trait StatelessInteractive<V: 'static>: Element<V> {
self
}
- fn context<C>(mut self, context: C) -> Self
- where
- Self: Sized,
- C: TryInto<DispatchContext>,
- C::Error: Debug,
- {
- self.stateless_interactivity().dispatch_context =
- context.try_into().expect("invalid dispatch context");
- self
- }
-
/// Capture the given action, fires during the capture phase
- fn capture_action<A: 'static>(
+ fn capture_action<A: Action>(
mut self,
listener: impl Fn(&mut V, &A, &mut ViewContext<V>) + 'static,
) -> Self
where
Self: Sized,
{
- self.stateless_interactivity().key_listeners.push((
+ self.stateless_interactivity().action_listeners.push((
TypeId::of::<A>(),
- Box::new(move |view, action, _dipatch_context, phase, cx| {
+ Box::new(move |view, action, phase, cx| {
let action = action.downcast_ref().unwrap();
if phase == DispatchPhase::Capture {
listener(view, action, cx)
}
- None
}),
));
self
}
/// Add a listener for the given action, fires during the bubble event phase
- fn on_action<A: 'static>(
+ fn on_action<A: Action>(
mut self,
listener: impl Fn(&mut V, &A, &mut ViewContext<V>) + 'static,
) -> Self
where
Self: Sized,
{
- self.stateless_interactivity().key_listeners.push((
+ self.stateless_interactivity().action_listeners.push((
TypeId::of::<A>(),
- Box::new(move |view, action, _dispatch_context, phase, cx| {
+ Box::new(move |view, action, phase, cx| {
let action = action.downcast_ref().unwrap();
if phase == DispatchPhase::Bubble {
listener(view, action, cx)
}
-
- None
}),
));
self
@@ -225,14 +211,11 @@ pub trait StatelessInteractive<V: 'static>: Element<V> {
where
Self: Sized,
{
- self.stateless_interactivity().key_listeners.push((
- TypeId::of::<KeyDownEvent>(),
- Box::new(move |view, event, _, phase, cx| {
- let event = event.downcast_ref().unwrap();
- listener(view, event, phase, cx);
- None
- }),
- ));
+ self.stateless_interactivity()
+ .key_down_listeners
+ .push(Box::new(move |view, event, phase, cx| {
+ listener(view, event, phase, cx)
+ }));
self
}
@@ -243,14 +226,11 @@ pub trait StatelessInteractive<V: 'static>: Element<V> {
where
Self: Sized,
{
- self.stateless_interactivity().key_listeners.push((
- TypeId::of::<KeyUpEvent>(),
- Box::new(move |view, event, _, phase, cx| {
- let event = event.downcast_ref().unwrap();
- listener(view, event, phase, cx);
- None
- }),
- ));
+ self.stateless_interactivity()
+ .key_up_listeners
+ .push(Box::new(move |view, event, phase, cx| {
+ listener(view, event, phase, cx)
+ }));
self
}
@@ -396,43 +376,6 @@ pub trait ElementInteractivity<V: 'static>: 'static {
fn as_stateful(&self) -> Option<&StatefulInteractivity<V>>;
fn as_stateful_mut(&mut self) -> Option<&mut StatefulInteractivity<V>>;
- fn initialize<R>(
- &mut self,
- cx: &mut ViewContext<V>,
- f: impl FnOnce(&mut ViewContext<V>) -> R,
- ) -> R {
- if let Some(stateful) = self.as_stateful_mut() {
- cx.with_element_id(stateful.id.clone(), |global_id, cx| {
- // In addition to any key down/up listeners registered directly on the element,
- // we also add a key listener to match actions from the keymap.
- stateful.key_listeners.push((
- TypeId::of::<KeyDownEvent>(),
- Box::new(move |_, key_down, context, phase, cx| {
- if phase == DispatchPhase::Bubble {
- let key_down = key_down.downcast_ref::<KeyDownEvent>().unwrap();
- if let KeyMatch::Some(action) =
- cx.match_keystroke(&global_id, &key_down.keystroke, context)
- {
- return Some(action);
- }
- }
-
- None
- }),
- ));
-
- cx.with_key_dispatch_context(stateful.dispatch_context.clone(), |cx| {
- cx.with_key_listeners(mem::take(&mut stateful.key_listeners), f)
- })
- })
- } else {
- let stateless = self.as_stateless_mut();
- cx.with_key_dispatch_context(stateless.dispatch_context.clone(), |cx| {
- cx.with_key_listeners(mem::take(&mut stateless.key_listeners), f)
- })
- }
- }
-
fn refine_style(
&self,
style: &mut Style,
@@ -487,6 +430,26 @@ pub trait ElementInteractivity<V: 'static>: 'static {
}
}
+ fn initialize(&mut self, cx: &mut ViewContext<V>) {
+ let stateless = self.as_stateless_mut();
+
+ for listener in stateless.key_down_listeners.drain(..) {
+ cx.on_key_event(move |state, event: &KeyDownEvent, phase, cx| {
+ listener(state, event, phase, cx);
+ })
+ }
+
+ for listener in stateless.key_up_listeners.drain(..) {
+ cx.on_key_event(move |state, event: &KeyUpEvent, phase, cx| {
+ listener(state, event, phase, cx);
+ })
+ }
+
+ for (action_type, listener) in stateless.action_listeners.drain(..) {
+ cx.on_action(action_type, listener)
+ }
+ }
+
fn paint(
&mut self,
bounds: Bounds<Pixels>,
@@ -808,12 +771,14 @@ impl<V: 'static> ElementInteractivity<V> for StatefulInteractivity<V> {
type DropListener<V> = dyn Fn(&mut V, AnyView, &mut ViewContext<V>) + 'static;
pub struct StatelessInteractivity<V> {
- pub dispatch_context: DispatchContext,
+ pub dispatch_context: KeyContext,
pub mouse_down_listeners: SmallVec<[MouseDownListener<V>; 2]>,
pub mouse_up_listeners: SmallVec<[MouseUpListener<V>; 2]>,
pub mouse_move_listeners: SmallVec<[MouseMoveListener<V>; 2]>,
pub scroll_wheel_listeners: SmallVec<[ScrollWheelListener<V>; 2]>,
- pub key_listeners: SmallVec<[(TypeId, KeyListener<V>); 32]>,
+ pub key_down_listeners: SmallVec<[KeyDownListener<V>; 2]>,
+ pub key_up_listeners: SmallVec<[KeyUpListener<V>; 2]>,
+ pub action_listeners: SmallVec<[(TypeId, ActionListener<V>); 8]>,
pub hover_style: StyleRefinement,
pub group_hover_style: Option<GroupStyle>,
drag_over_styles: SmallVec<[(TypeId, StyleRefinement); 2]>,
@@ -910,12 +875,14 @@ impl InteractiveElementState {
impl<V> Default for StatelessInteractivity<V> {
fn default() -> Self {
Self {
- dispatch_context: DispatchContext::default(),
+ dispatch_context: KeyContext::default(),
mouse_down_listeners: SmallVec::new(),
mouse_up_listeners: SmallVec::new(),
mouse_move_listeners: SmallVec::new(),
scroll_wheel_listeners: SmallVec::new(),
- key_listeners: SmallVec::new(),
+ key_down_listeners: SmallVec::new(),
+ key_up_listeners: SmallVec::new(),
+ action_listeners: SmallVec::new(),
hover_style: StyleRefinement::default(),
group_hover_style: None,
drag_over_styles: SmallVec::new(),
@@ -1250,16 +1217,14 @@ pub(crate) type HoverListener<V> = Box<dyn Fn(&mut V, bool, &mut ViewContext<V>)
pub(crate) type TooltipBuilder<V> = Arc<dyn Fn(&mut V, &mut ViewContext<V>) -> AnyView + 'static>;
-pub type KeyListener<V> = Box<
- dyn Fn(
- &mut V,
- &dyn Any,
- &[&DispatchContext],
- DispatchPhase,
- &mut ViewContext<V>,
- ) -> Option<Box<dyn Action>>
- + 'static,
->;
+pub(crate) type KeyDownListener<V> =
+ Box<dyn Fn(&mut V, &KeyDownEvent, DispatchPhase, &mut ViewContext<V>) + 'static>;
+
+pub(crate) type KeyUpListener<V> =
+ Box<dyn Fn(&mut V, &KeyUpEvent, DispatchPhase, &mut ViewContext<V>) + 'static>;
+
+pub type ActionListener<V> =
+ Box<dyn Fn(&mut V, &dyn Any, DispatchPhase, &mut ViewContext<V>) + 'static>;
#[cfg(test)]
mod test {
@@ -1282,9 +1247,10 @@ mod test {
fn render(&mut self, _: &mut gpui::ViewContext<Self>) -> Self::Element {
div().id("testview").child(
div()
+ .context("test")
+ .track_focus(&self.focus_handle)
.on_key_down(|this: &mut TestView, _, _, _| this.saw_key_down = true)
- .on_action(|this: &mut TestView, _: &TestAction, _| this.saw_action = true)
- .track_focus(&self.focus_handle),
+ .on_action(|this: &mut TestView, _: &TestAction, _| this.saw_action = true),
)
}
}
@@ -0,0 +1,465 @@
+use crate::{
+ build_action_from_type, Action, Bounds, DispatchPhase, Element, FocusEvent, FocusHandle,
+ FocusId, KeyBinding, KeyContext, KeyMatch, Keymap, Keystroke, KeystrokeMatcher, MouseDownEvent,
+ Pixels, Style, StyleRefinement, ViewContext, WindowContext,
+};
+use collections::HashMap;
+use parking_lot::Mutex;
+use refineable::Refineable;
+use smallvec::SmallVec;
+use std::{
+ any::{Any, TypeId},
+ rc::Rc,
+ sync::Arc,
+};
+use util::ResultExt;
+
+pub type FocusListeners<V> = SmallVec<[FocusListener<V>; 2]>;
+pub type FocusListener<V> =
+ Box<dyn Fn(&mut V, &FocusHandle, &FocusEvent, &mut ViewContext<V>) + 'static>;
+
+#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)]
+pub struct DispatchNodeId(usize);
+
+pub(crate) struct DispatchTree {
+ node_stack: Vec<DispatchNodeId>,
+ context_stack: Vec<KeyContext>,
+ nodes: Vec<DispatchNode>,
+ focusable_node_ids: HashMap<FocusId, DispatchNodeId>,
+ keystroke_matchers: HashMap<SmallVec<[KeyContext; 4]>, KeystrokeMatcher>,
+ keymap: Arc<Mutex<Keymap>>,
+}
+
+#[derive(Default)]
+pub(crate) struct DispatchNode {
+ pub key_listeners: SmallVec<[KeyListener; 2]>,
+ pub action_listeners: SmallVec<[DispatchActionListener; 16]>,
+ pub context: KeyContext,
+ parent: Option<DispatchNodeId>,
+}
+
+type KeyListener = Rc<dyn Fn(&dyn Any, DispatchPhase, &mut WindowContext)>;
+
+#[derive(Clone)]
+pub(crate) struct DispatchActionListener {
+ pub(crate) action_type: TypeId,
+ pub(crate) listener: Rc<dyn Fn(&dyn Any, DispatchPhase, &mut WindowContext)>,
+}
+
+impl DispatchTree {
+ pub fn new(keymap: Arc<Mutex<Keymap>>) -> Self {
+ Self {
+ node_stack: Vec::new(),
+ context_stack: Vec::new(),
+ nodes: Vec::new(),
+ focusable_node_ids: HashMap::default(),
+ keystroke_matchers: HashMap::default(),
+ keymap,
+ }
+ }
+
+ pub fn clear(&mut self) {
+ self.node_stack.clear();
+ self.nodes.clear();
+ self.context_stack.clear();
+ self.focusable_node_ids.clear();
+ self.keystroke_matchers.clear();
+ }
+
+ pub fn push_node(&mut self, context: KeyContext, old_dispatcher: &mut Self) {
+ let parent = self.node_stack.last().copied();
+ let node_id = DispatchNodeId(self.nodes.len());
+ self.nodes.push(DispatchNode {
+ parent,
+ ..Default::default()
+ });
+ self.node_stack.push(node_id);
+ if !context.is_empty() {
+ self.active_node().context = context.clone();
+ self.context_stack.push(context);
+ if let Some((context_stack, matcher)) = old_dispatcher
+ .keystroke_matchers
+ .remove_entry(self.context_stack.as_slice())
+ {
+ self.keystroke_matchers.insert(context_stack, matcher);
+ }
+ }
+ }
+
+ pub fn pop_node(&mut self) {
+ let node_id = self.node_stack.pop().unwrap();
+ if !self.nodes[node_id.0].context.is_empty() {
+ self.context_stack.pop();
+ }
+ }
+
+ pub fn on_key_event(&mut self, listener: KeyListener) {
+ self.active_node().key_listeners.push(listener);
+ }
+
+ pub fn on_action(
+ &mut self,
+ action_type: TypeId,
+ listener: Rc<dyn Fn(&dyn Any, DispatchPhase, &mut WindowContext)>,
+ ) {
+ self.active_node()
+ .action_listeners
+ .push(DispatchActionListener {
+ action_type,
+ listener,
+ });
+ }
+
+ pub fn make_focusable(&mut self, focus_id: FocusId) {
+ self.focusable_node_ids
+ .insert(focus_id, self.active_node_id());
+ }
+
+ pub fn focus_contains(&self, parent: FocusId, child: FocusId) -> bool {
+ if parent == child {
+ return true;
+ }
+
+ if let Some(parent_node_id) = self.focusable_node_ids.get(&parent) {
+ let mut current_node_id = self.focusable_node_ids.get(&child).copied();
+ while let Some(node_id) = current_node_id {
+ if node_id == *parent_node_id {
+ return true;
+ }
+ current_node_id = self.nodes[node_id.0].parent;
+ }
+ }
+ false
+ }
+
+ pub fn available_actions(&self, target: FocusId) -> Vec<Box<dyn Action>> {
+ let mut actions = Vec::new();
+ if let Some(node) = self.focusable_node_ids.get(&target) {
+ for node_id in self.dispatch_path(*node) {
+ let node = &self.nodes[node_id.0];
+ for DispatchActionListener { action_type, .. } in &node.action_listeners {
+ actions.extend(build_action_from_type(action_type).log_err());
+ }
+ }
+ }
+ actions
+ }
+
+ pub fn bindings_for_action(&self, action: &dyn Action) -> Vec<KeyBinding> {
+ self.keymap
+ .lock()
+ .bindings_for_action(action.type_id())
+ .filter(|candidate| candidate.action.partial_eq(action))
+ .cloned()
+ .collect()
+ }
+
+ pub fn dispatch_key(
+ &mut self,
+ keystroke: &Keystroke,
+ context: &[KeyContext],
+ ) -> Option<Box<dyn Action>> {
+ if !self.keystroke_matchers.contains_key(context) {
+ let keystroke_contexts = context.iter().cloned().collect();
+ self.keystroke_matchers.insert(
+ keystroke_contexts,
+ KeystrokeMatcher::new(self.keymap.clone()),
+ );
+ }
+
+ let keystroke_matcher = self.keystroke_matchers.get_mut(context).unwrap();
+ if let KeyMatch::Some(action) = keystroke_matcher.match_keystroke(keystroke, context) {
+ // Clear all pending keystrokes when an action has been found.
+ for keystroke_matcher in self.keystroke_matchers.values_mut() {
+ keystroke_matcher.clear_pending();
+ }
+
+ Some(action)
+ } else {
+ None
+ }
+ }
+
+ pub fn dispatch_path(&self, target: DispatchNodeId) -> SmallVec<[DispatchNodeId; 32]> {
+ let mut dispatch_path: SmallVec<[DispatchNodeId; 32]> = SmallVec::new();
+ let mut current_node_id = Some(target);
+ while let Some(node_id) = current_node_id {
+ dispatch_path.push(node_id);
+ current_node_id = self.nodes[node_id.0].parent;
+ }
+ dispatch_path.reverse(); // Reverse the path so it goes from the root to the focused node.
+ dispatch_path
+ }
+
+ pub fn node(&self, node_id: DispatchNodeId) -> &DispatchNode {
+ &self.nodes[node_id.0]
+ }
+
+ fn active_node(&mut self) -> &mut DispatchNode {
+ let active_node_id = self.active_node_id();
+ &mut self.nodes[active_node_id.0]
+ }
+
+ pub fn focusable_node_id(&self, target: FocusId) -> Option<DispatchNodeId> {
+ self.focusable_node_ids.get(&target).copied()
+ }
+
+ fn active_node_id(&self) -> DispatchNodeId {
+ *self.node_stack.last().unwrap()
+ }
+}
+
+pub trait KeyDispatch<V: 'static>: 'static {
+ fn as_focusable(&self) -> Option<&FocusableKeyDispatch<V>>;
+ fn as_focusable_mut(&mut self) -> Option<&mut FocusableKeyDispatch<V>>;
+ fn key_context(&self) -> &KeyContext;
+ fn key_context_mut(&mut self) -> &mut KeyContext;
+
+ fn initialize<R>(
+ &mut self,
+ focus_handle: Option<FocusHandle>,
+ cx: &mut ViewContext<V>,
+ f: impl FnOnce(Option<FocusHandle>, &mut ViewContext<V>) -> R,
+ ) -> R {
+ let focus_handle = if let Some(focusable) = self.as_focusable_mut() {
+ let focus_handle = focusable
+ .focus_handle
+ .get_or_insert_with(|| focus_handle.unwrap_or_else(|| cx.focus_handle()))
+ .clone();
+ for listener in focusable.focus_listeners.drain(..) {
+ let focus_handle = focus_handle.clone();
+ cx.on_focus_changed(move |view, event, cx| {
+ listener(view, &focus_handle, event, cx)
+ });
+ }
+ Some(focus_handle)
+ } else {
+ None
+ };
+
+ cx.with_key_dispatch(self.key_context().clone(), focus_handle, f)
+ }
+
+ fn refine_style(&self, style: &mut Style, cx: &WindowContext) {
+ if let Some(focusable) = self.as_focusable() {
+ let focus_handle = focusable
+ .focus_handle
+ .as_ref()
+ .expect("must call initialize before refine_style");
+ if focus_handle.contains_focused(cx) {
+ style.refine(&focusable.focus_in_style);
+ }
+
+ if focus_handle.within_focused(cx) {
+ style.refine(&focusable.in_focus_style);
+ }
+
+ if focus_handle.is_focused(cx) {
+ style.refine(&focusable.focus_style);
+ }
+ }
+ }
+
+ fn paint(&self, bounds: Bounds<Pixels>, cx: &mut WindowContext) {
+ if let Some(focusable) = self.as_focusable() {
+ let focus_handle = focusable
+ .focus_handle
+ .clone()
+ .expect("must call initialize before paint");
+ cx.on_mouse_event(move |event: &MouseDownEvent, phase, cx| {
+ if phase == DispatchPhase::Bubble && bounds.contains_point(&event.position) {
+ if !cx.default_prevented() {
+ cx.focus(&focus_handle);
+ cx.prevent_default();
+ }
+ }
+ })
+ }
+ }
+}
+
+pub struct FocusableKeyDispatch<V> {
+ pub non_focusable: NonFocusableKeyDispatch,
+ pub focus_handle: Option<FocusHandle>,
+ pub focus_listeners: FocusListeners<V>,
+ pub focus_style: StyleRefinement,
+ pub focus_in_style: StyleRefinement,
+ pub in_focus_style: StyleRefinement,
+}
+
+impl<V> FocusableKeyDispatch<V> {
+ pub fn new(non_focusable: NonFocusableKeyDispatch) -> Self {
+ Self {
+ non_focusable,
+ focus_handle: None,
+ focus_listeners: FocusListeners::default(),
+ focus_style: StyleRefinement::default(),
+ focus_in_style: StyleRefinement::default(),
+ in_focus_style: StyleRefinement::default(),
+ }
+ }
+
+ pub fn tracked(non_focusable: NonFocusableKeyDispatch, handle: &FocusHandle) -> Self {
+ Self {
+ non_focusable,
+ focus_handle: Some(handle.clone()),
+ focus_listeners: FocusListeners::default(),
+ focus_style: StyleRefinement::default(),
+ focus_in_style: StyleRefinement::default(),
+ in_focus_style: StyleRefinement::default(),
+ }
+ }
+}
+
+impl<V: 'static> KeyDispatch<V> for FocusableKeyDispatch<V> {
+ fn as_focusable(&self) -> Option<&FocusableKeyDispatch<V>> {
+ Some(self)
+ }
+
+ fn as_focusable_mut(&mut self) -> Option<&mut FocusableKeyDispatch<V>> {
+ Some(self)
+ }
+
+ fn key_context(&self) -> &KeyContext {
+ &self.non_focusable.key_context
+ }
+
+ fn key_context_mut(&mut self) -> &mut KeyContext {
+ &mut self.non_focusable.key_context
+ }
+}
+
+#[derive(Default)]
+pub struct NonFocusableKeyDispatch {
+ pub(crate) key_context: KeyContext,
+}
+
+impl<V: 'static> KeyDispatch<V> for NonFocusableKeyDispatch {
+ fn as_focusable(&self) -> Option<&FocusableKeyDispatch<V>> {
+ None
+ }
+
+ fn as_focusable_mut(&mut self) -> Option<&mut FocusableKeyDispatch<V>> {
+ None
+ }
+
+ fn key_context(&self) -> &KeyContext {
+ &self.key_context
+ }
+
+ fn key_context_mut(&mut self) -> &mut KeyContext {
+ &mut self.key_context
+ }
+}
+
+pub trait Focusable<V: 'static>: Element<V> {
+ fn focus_listeners(&mut self) -> &mut FocusListeners<V>;
+ fn set_focus_style(&mut self, style: StyleRefinement);
+ fn set_focus_in_style(&mut self, style: StyleRefinement);
+ fn set_in_focus_style(&mut self, style: StyleRefinement);
+
+ fn focus(mut self, f: impl FnOnce(StyleRefinement) -> StyleRefinement) -> Self
+ where
+ Self: Sized,
+ {
+ self.set_focus_style(f(StyleRefinement::default()));
+ self
+ }
+
+ fn focus_in(mut self, f: impl FnOnce(StyleRefinement) -> StyleRefinement) -> Self
+ where
+ Self: Sized,
+ {
+ self.set_focus_in_style(f(StyleRefinement::default()));
+ self
+ }
+
+ fn in_focus(mut self, f: impl FnOnce(StyleRefinement) -> StyleRefinement) -> Self
+ where
+ Self: Sized,
+ {
+ self.set_in_focus_style(f(StyleRefinement::default()));
+ self
+ }
+
+ fn on_focus(
+ mut self,
+ listener: impl Fn(&mut V, &FocusEvent, &mut ViewContext<V>) + 'static,
+ ) -> Self
+ where
+ Self: Sized,
+ {
+ self.focus_listeners()
+ .push(Box::new(move |view, focus_handle, event, cx| {
+ if event.focused.as_ref() == Some(focus_handle) {
+ listener(view, event, cx)
+ }
+ }));
+ self
+ }
+
+ fn on_blur(
+ mut self,
+ listener: impl Fn(&mut V, &FocusEvent, &mut ViewContext<V>) + 'static,
+ ) -> Self
+ where
+ Self: Sized,
+ {
+ self.focus_listeners()
+ .push(Box::new(move |view, focus_handle, event, cx| {
+ if event.blurred.as_ref() == Some(focus_handle) {
+ listener(view, event, cx)
+ }
+ }));
+ self
+ }
+
+ fn on_focus_in(
+ mut self,
+ listener: impl Fn(&mut V, &FocusEvent, &mut ViewContext<V>) + 'static,
+ ) -> Self
+ where
+ Self: Sized,
+ {
+ self.focus_listeners()
+ .push(Box::new(move |view, focus_handle, event, cx| {
+ let descendant_blurred = event
+ .blurred
+ .as_ref()
+ .map_or(false, |blurred| focus_handle.contains(blurred, cx));
+ let descendant_focused = event
+ .focused
+ .as_ref()
+ .map_or(false, |focused| focus_handle.contains(focused, cx));
+
+ if !descendant_blurred && descendant_focused {
+ listener(view, event, cx)
+ }
+ }));
+ self
+ }
+
+ fn on_focus_out(
+ mut self,
+ listener: impl Fn(&mut V, &FocusEvent, &mut ViewContext<V>) + 'static,
+ ) -> Self
+ where
+ Self: Sized,
+ {
+ self.focus_listeners()
+ .push(Box::new(move |view, focus_handle, event, cx| {
+ let descendant_blurred = event
+ .blurred
+ .as_ref()
+ .map_or(false, |blurred| focus_handle.contains(blurred, cx));
+ let descendant_focused = event
+ .focused
+ .as_ref()
+ .map_or(false, |focused| focus_handle.contains(focused, cx));
+ if descendant_blurred && !descendant_focused {
+ listener(view, event, cx)
+ }
+ }));
+ self
+ }
+}
@@ -1,11 +1,21 @@
-use crate::{Action, DispatchContext, DispatchContextPredicate, KeyMatch, Keystroke};
+use crate::{Action, KeyBindingContextPredicate, KeyContext, KeyMatch, Keystroke};
use anyhow::Result;
use smallvec::SmallVec;
pub struct KeyBinding {
- action: Box<dyn Action>,
- pub(super) keystrokes: SmallVec<[Keystroke; 2]>,
- pub(super) context_predicate: Option<DispatchContextPredicate>,
+ pub(crate) action: Box<dyn Action>,
+ pub(crate) keystrokes: SmallVec<[Keystroke; 2]>,
+ pub(crate) context_predicate: Option<KeyBindingContextPredicate>,
+}
+
+impl Clone for KeyBinding {
+ fn clone(&self) -> Self {
+ KeyBinding {
+ action: self.action.boxed_clone(),
+ keystrokes: self.keystrokes.clone(),
+ context_predicate: self.context_predicate.clone(),
+ }
+ }
}
impl KeyBinding {
@@ -15,7 +25,7 @@ impl KeyBinding {
pub fn load(keystrokes: &str, action: Box<dyn Action>, context: Option<&str>) -> Result<Self> {
let context = if let Some(context) = context {
- Some(DispatchContextPredicate::parse(context)?)
+ Some(KeyBindingContextPredicate::parse(context)?)
} else {
None
};
@@ -32,7 +42,7 @@ impl KeyBinding {
})
}
- pub fn matches_context(&self, contexts: &[&DispatchContext]) -> bool {
+ pub fn matches_context(&self, contexts: &[KeyContext]) -> bool {
self.context_predicate
.as_ref()
.map(|predicate| predicate.eval(contexts))
@@ -42,7 +52,7 @@ impl KeyBinding {
pub fn match_keystrokes(
&self,
pending_keystrokes: &[Keystroke],
- contexts: &[&DispatchContext],
+ contexts: &[KeyContext],
) -> KeyMatch {
if self.keystrokes.as_ref().starts_with(&pending_keystrokes)
&& self.matches_context(contexts)
@@ -61,7 +71,7 @@ impl KeyBinding {
pub fn keystrokes_for_action(
&self,
action: &dyn Action,
- contexts: &[&DispatchContext],
+ contexts: &[KeyContext],
) -> Option<SmallVec<[Keystroke; 2]>> {
if self.action.partial_eq(action) && self.matches_context(contexts) {
Some(self.keystrokes.clone())
@@ -0,0 +1,449 @@
+use crate::SharedString;
+use anyhow::{anyhow, Result};
+use smallvec::SmallVec;
+use std::fmt;
+
+#[derive(Clone, Default, Eq, PartialEq, Hash)]
+pub struct KeyContext(SmallVec<[ContextEntry; 8]>);
+
+#[derive(Clone, Debug, Eq, PartialEq, Hash)]
+struct ContextEntry {
+ key: SharedString,
+ value: Option<SharedString>,
+}
+
+impl<'a> TryFrom<&'a str> for KeyContext {
+ type Error = anyhow::Error;
+
+ fn try_from(value: &'a str) -> Result<Self> {
+ Self::parse(value)
+ }
+}
+
+impl KeyContext {
+ pub fn parse(source: &str) -> Result<Self> {
+ let mut context = Self::default();
+ let source = skip_whitespace(source);
+ Self::parse_expr(&source, &mut context)?;
+ Ok(context)
+ }
+
+ fn parse_expr(mut source: &str, context: &mut Self) -> Result<()> {
+ if source.is_empty() {
+ return Ok(());
+ }
+
+ let key = source
+ .chars()
+ .take_while(|c| is_identifier_char(*c))
+ .collect::<String>();
+ source = skip_whitespace(&source[key.len()..]);
+ if let Some(suffix) = source.strip_prefix('=') {
+ source = skip_whitespace(suffix);
+ let value = source
+ .chars()
+ .take_while(|c| is_identifier_char(*c))
+ .collect::<String>();
+ source = skip_whitespace(&source[value.len()..]);
+ context.set(key, value);
+ } else {
+ context.add(key);
+ }
+
+ Self::parse_expr(source, context)
+ }
+
+ pub fn is_empty(&self) -> bool {
+ self.0.is_empty()
+ }
+
+ pub fn clear(&mut self) {
+ self.0.clear();
+ }
+
+ pub fn extend(&mut self, other: &Self) {
+ for entry in &other.0 {
+ if !self.contains(&entry.key) {
+ self.0.push(entry.clone());
+ }
+ }
+ }
+
+ pub fn add<I: Into<SharedString>>(&mut self, identifier: I) {
+ let key = identifier.into();
+
+ if !self.contains(&key) {
+ self.0.push(ContextEntry { key, value: None })
+ }
+ }
+
+ pub fn set<S1: Into<SharedString>, S2: Into<SharedString>>(&mut self, key: S1, value: S2) {
+ let key = key.into();
+ if !self.contains(&key) {
+ self.0.push(ContextEntry {
+ key,
+ value: Some(value.into()),
+ })
+ }
+ }
+
+ pub fn contains(&self, key: &str) -> bool {
+ self.0.iter().any(|entry| entry.key.as_ref() == key)
+ }
+
+ pub fn get(&self, key: &str) -> Option<&SharedString> {
+ self.0
+ .iter()
+ .find(|entry| entry.key.as_ref() == key)?
+ .value
+ .as_ref()
+ }
+}
+
+impl fmt::Debug for KeyContext {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ let mut entries = self.0.iter().peekable();
+ while let Some(entry) = entries.next() {
+ if let Some(ref value) = entry.value {
+ write!(f, "{}={}", entry.key, value)?;
+ } else {
+ write!(f, "{}", entry.key)?;
+ }
+ if entries.peek().is_some() {
+ write!(f, " ")?;
+ }
+ }
+ Ok(())
+ }
+}
+
+#[derive(Clone, Debug, Eq, PartialEq, Hash)]
+pub enum KeyBindingContextPredicate {
+ Identifier(SharedString),
+ Equal(SharedString, SharedString),
+ NotEqual(SharedString, SharedString),
+ Child(
+ Box<KeyBindingContextPredicate>,
+ Box<KeyBindingContextPredicate>,
+ ),
+ Not(Box<KeyBindingContextPredicate>),
+ And(
+ Box<KeyBindingContextPredicate>,
+ Box<KeyBindingContextPredicate>,
+ ),
+ Or(
+ Box<KeyBindingContextPredicate>,
+ Box<KeyBindingContextPredicate>,
+ ),
+}
+
+impl KeyBindingContextPredicate {
+ pub fn parse(source: &str) -> Result<Self> {
+ let source = skip_whitespace(source);
+ let (predicate, rest) = Self::parse_expr(source, 0)?;
+ if let Some(next) = rest.chars().next() {
+ Err(anyhow!("unexpected character {next:?}"))
+ } else {
+ Ok(predicate)
+ }
+ }
+
+ pub fn eval(&self, contexts: &[KeyContext]) -> bool {
+ let Some(context) = contexts.last() else {
+ return false;
+ };
+ match self {
+ Self::Identifier(name) => context.contains(name),
+ Self::Equal(left, right) => context
+ .get(left)
+ .map(|value| value == right)
+ .unwrap_or(false),
+ Self::NotEqual(left, right) => context
+ .get(left)
+ .map(|value| value != right)
+ .unwrap_or(true),
+ Self::Not(pred) => !pred.eval(contexts),
+ Self::Child(parent, child) => {
+ parent.eval(&contexts[..contexts.len() - 1]) && child.eval(contexts)
+ }
+ Self::And(left, right) => left.eval(contexts) && right.eval(contexts),
+ Self::Or(left, right) => left.eval(contexts) || right.eval(contexts),
+ }
+ }
+
+ fn parse_expr(mut source: &str, min_precedence: u32) -> anyhow::Result<(Self, &str)> {
+ type Op = fn(
+ KeyBindingContextPredicate,
+ KeyBindingContextPredicate,
+ ) -> Result<KeyBindingContextPredicate>;
+
+ let (mut predicate, rest) = Self::parse_primary(source)?;
+ source = rest;
+
+ 'parse: loop {
+ for (operator, precedence, constructor) in [
+ (">", PRECEDENCE_CHILD, Self::new_child as Op),
+ ("&&", PRECEDENCE_AND, Self::new_and as Op),
+ ("||", PRECEDENCE_OR, Self::new_or as Op),
+ ("==", PRECEDENCE_EQ, Self::new_eq as Op),
+ ("!=", PRECEDENCE_EQ, Self::new_neq as Op),
+ ] {
+ if source.starts_with(operator) && precedence >= min_precedence {
+ source = skip_whitespace(&source[operator.len()..]);
+ let (right, rest) = Self::parse_expr(source, precedence + 1)?;
+ predicate = constructor(predicate, right)?;
+ source = rest;
+ continue 'parse;
+ }
+ }
+ break;
+ }
+
+ Ok((predicate, source))
+ }
+
+ fn parse_primary(mut source: &str) -> anyhow::Result<(Self, &str)> {
+ let next = source
+ .chars()
+ .next()
+ .ok_or_else(|| anyhow!("unexpected eof"))?;
+ match next {
+ '(' => {
+ source = skip_whitespace(&source[1..]);
+ let (predicate, rest) = Self::parse_expr(source, 0)?;
+ if rest.starts_with(')') {
+ source = skip_whitespace(&rest[1..]);
+ Ok((predicate, source))
+ } else {
+ Err(anyhow!("expected a ')'"))
+ }
+ }
+ '!' => {
+ let source = skip_whitespace(&source[1..]);
+ let (predicate, source) = Self::parse_expr(&source, PRECEDENCE_NOT)?;
+ Ok((KeyBindingContextPredicate::Not(Box::new(predicate)), source))
+ }
+ _ if is_identifier_char(next) => {
+ let len = source
+ .find(|c: char| !is_identifier_char(c))
+ .unwrap_or(source.len());
+ let (identifier, rest) = source.split_at(len);
+ source = skip_whitespace(rest);
+ Ok((
+ KeyBindingContextPredicate::Identifier(identifier.to_string().into()),
+ source,
+ ))
+ }
+ _ => Err(anyhow!("unexpected character {next:?}")),
+ }
+ }
+
+ fn new_or(self, other: Self) -> Result<Self> {
+ Ok(Self::Or(Box::new(self), Box::new(other)))
+ }
+
+ fn new_and(self, other: Self) -> Result<Self> {
+ Ok(Self::And(Box::new(self), Box::new(other)))
+ }
+
+ fn new_child(self, other: Self) -> Result<Self> {
+ Ok(Self::Child(Box::new(self), Box::new(other)))
+ }
+
+ fn new_eq(self, other: Self) -> Result<Self> {
+ if let (Self::Identifier(left), Self::Identifier(right)) = (self, other) {
+ Ok(Self::Equal(left, right))
+ } else {
+ Err(anyhow!("operands must be identifiers"))
+ }
+ }
+
+ fn new_neq(self, other: Self) -> Result<Self> {
+ if let (Self::Identifier(left), Self::Identifier(right)) = (self, other) {
+ Ok(Self::NotEqual(left, right))
+ } else {
+ Err(anyhow!("operands must be identifiers"))
+ }
+ }
+}
+
+const PRECEDENCE_CHILD: u32 = 1;
+const PRECEDENCE_OR: u32 = 2;
+const PRECEDENCE_AND: u32 = 3;
+const PRECEDENCE_EQ: u32 = 4;
+const PRECEDENCE_NOT: u32 = 5;
+
+fn is_identifier_char(c: char) -> bool {
+ c.is_alphanumeric() || c == '_' || c == '-'
+}
+
+fn skip_whitespace(source: &str) -> &str {
+ let len = source
+ .find(|c: char| !c.is_whitespace())
+ .unwrap_or(source.len());
+ &source[len..]
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use crate as gpui;
+ use KeyBindingContextPredicate::*;
+
+ #[test]
+ fn test_actions_definition() {
+ {
+ actions!(A, B, C, D, E, F, G);
+ }
+
+ {
+ actions!(
+ A,
+ B,
+ C,
+ D,
+ E,
+ F,
+ G, // Don't wrap, test the trailing comma
+ );
+ }
+ }
+
+ #[test]
+ fn test_parse_context() {
+ let mut expected = KeyContext::default();
+ expected.add("baz");
+ expected.set("foo", "bar");
+ assert_eq!(KeyContext::parse("baz foo=bar").unwrap(), expected);
+ assert_eq!(KeyContext::parse("baz foo = bar").unwrap(), expected);
+ assert_eq!(
+ KeyContext::parse(" baz foo = bar baz").unwrap(),
+ expected
+ );
+ assert_eq!(KeyContext::parse(" baz foo = bar").unwrap(), expected);
+ }
+
+ #[test]
+ fn test_parse_identifiers() {
+ // Identifiers
+ assert_eq!(
+ KeyBindingContextPredicate::parse("abc12").unwrap(),
+ Identifier("abc12".into())
+ );
+ assert_eq!(
+ KeyBindingContextPredicate::parse("_1a").unwrap(),
+ Identifier("_1a".into())
+ );
+ }
+
+ #[test]
+ fn test_parse_negations() {
+ assert_eq!(
+ KeyBindingContextPredicate::parse("!abc").unwrap(),
+ Not(Box::new(Identifier("abc".into())))
+ );
+ assert_eq!(
+ KeyBindingContextPredicate::parse(" ! ! abc").unwrap(),
+ Not(Box::new(Not(Box::new(Identifier("abc".into())))))
+ );
+ }
+
+ #[test]
+ fn test_parse_equality_operators() {
+ assert_eq!(
+ KeyBindingContextPredicate::parse("a == b").unwrap(),
+ Equal("a".into(), "b".into())
+ );
+ assert_eq!(
+ KeyBindingContextPredicate::parse("c!=d").unwrap(),
+ NotEqual("c".into(), "d".into())
+ );
+ assert_eq!(
+ KeyBindingContextPredicate::parse("c == !d")
+ .unwrap_err()
+ .to_string(),
+ "operands must be identifiers"
+ );
+ }
+
+ #[test]
+ fn test_parse_boolean_operators() {
+ assert_eq!(
+ KeyBindingContextPredicate::parse("a || b").unwrap(),
+ Or(
+ Box::new(Identifier("a".into())),
+ Box::new(Identifier("b".into()))
+ )
+ );
+ assert_eq!(
+ KeyBindingContextPredicate::parse("a || !b && c").unwrap(),
+ Or(
+ Box::new(Identifier("a".into())),
+ Box::new(And(
+ Box::new(Not(Box::new(Identifier("b".into())))),
+ Box::new(Identifier("c".into()))
+ ))
+ )
+ );
+ assert_eq!(
+ KeyBindingContextPredicate::parse("a && b || c&&d").unwrap(),
+ Or(
+ Box::new(And(
+ Box::new(Identifier("a".into())),
+ Box::new(Identifier("b".into()))
+ )),
+ Box::new(And(
+ Box::new(Identifier("c".into())),
+ Box::new(Identifier("d".into()))
+ ))
+ )
+ );
+ assert_eq!(
+ KeyBindingContextPredicate::parse("a == b && c || d == e && f").unwrap(),
+ Or(
+ Box::new(And(
+ Box::new(Equal("a".into(), "b".into())),
+ Box::new(Identifier("c".into()))
+ )),
+ Box::new(And(
+ Box::new(Equal("d".into(), "e".into())),
+ Box::new(Identifier("f".into()))
+ ))
+ )
+ );
+ assert_eq!(
+ KeyBindingContextPredicate::parse("a && b && c && d").unwrap(),
+ And(
+ Box::new(And(
+ Box::new(And(
+ Box::new(Identifier("a".into())),
+ Box::new(Identifier("b".into()))
+ )),
+ Box::new(Identifier("c".into())),
+ )),
+ Box::new(Identifier("d".into()))
+ ),
+ );
+ }
+
+ #[test]
+ fn test_parse_parenthesized_expressions() {
+ assert_eq!(
+ KeyBindingContextPredicate::parse("a && (b == c || d != e)").unwrap(),
+ And(
+ Box::new(Identifier("a".into())),
+ Box::new(Or(
+ Box::new(Equal("b".into(), "c".into())),
+ Box::new(NotEqual("d".into(), "e".into())),
+ )),
+ ),
+ );
+ assert_eq!(
+ KeyBindingContextPredicate::parse(" ( a || b ) ").unwrap(),
+ Or(
+ Box::new(Identifier("a".into())),
+ Box::new(Identifier("b".into())),
+ )
+ );
+ }
+}
@@ -1,4 +1,4 @@
-use crate::{DispatchContextPredicate, KeyBinding, Keystroke};
+use crate::{KeyBinding, KeyBindingContextPredicate, Keystroke};
use collections::HashSet;
use smallvec::SmallVec;
use std::{any::TypeId, collections::HashMap};
@@ -11,7 +11,7 @@ pub struct Keymap {
bindings: Vec<KeyBinding>,
binding_indices_by_action_id: HashMap<TypeId, SmallVec<[usize; 3]>>,
disabled_keystrokes:
- HashMap<SmallVec<[Keystroke; 2]>, HashSet<Option<DispatchContextPredicate>>>,
+ HashMap<SmallVec<[Keystroke; 2]>, HashSet<Option<KeyBindingContextPredicate>>>,
version: KeymapVersion,
}
@@ -1,15 +1,15 @@
-use crate::{Action, DispatchContext, Keymap, KeymapVersion, Keystroke};
+use crate::{Action, KeyContext, Keymap, KeymapVersion, Keystroke};
use parking_lot::Mutex;
use smallvec::SmallVec;
use std::sync::Arc;
-pub struct KeyMatcher {
+pub struct KeystrokeMatcher {
pending_keystrokes: Vec<Keystroke>,
keymap: Arc<Mutex<Keymap>>,
keymap_version: KeymapVersion,
}
-impl KeyMatcher {
+impl KeystrokeMatcher {
pub fn new(keymap: Arc<Mutex<Keymap>>) -> Self {
let keymap_version = keymap.lock().version();
Self {
@@ -44,7 +44,7 @@ impl KeyMatcher {
pub fn match_keystroke(
&mut self,
keystroke: &Keystroke,
- context_stack: &[&DispatchContext],
+ context_stack: &[KeyContext],
) -> KeyMatch {
let keymap = self.keymap.lock();
// Clear pending keystrokes if the keymap has changed since the last matched keystroke.
@@ -86,7 +86,7 @@ impl KeyMatcher {
pub fn keystrokes_for_action(
&self,
action: &dyn Action,
- contexts: &[&DispatchContext],
+ contexts: &[KeyContext],
) -> Option<SmallVec<[Keystroke; 2]>> {
self.keymap
.lock()
@@ -1,7 +1,9 @@
mod binding;
+mod context;
mod keymap;
mod matcher;
pub use binding::*;
+pub use context::*;
pub use keymap::*;
pub use matcher::*;
@@ -0,0 +1 @@
+pub use crate::{Context, ParentElement, Refineable};
@@ -184,10 +184,6 @@ impl AnyView {
.compute_layout(layout_id, available_space);
(self.paint)(self, &mut rendered_element, cx);
}
-
- pub(crate) fn draw_dispatch_stack(&self, cx: &mut WindowContext) {
- (self.initialize)(self, cx);
- }
}
impl<V: 'static> Component<V> for AnyView {
@@ -1,15 +1,15 @@
use crate::{
- build_action_from_type, px, size, Action, AnyBox, AnyDrag, AnyView, AppContext,
+ key_dispatch::DispatchActionListener, px, size, Action, AnyBox, AnyDrag, AnyView, AppContext,
AsyncWindowContext, AvailableSpace, Bounds, BoxShadow, Context, Corners, CursorStyle,
- DevicePixels, DispatchContext, DisplayId, Edges, Effect, Entity, EntityId, EventEmitter,
- FileDropEvent, FocusEvent, FontId, GlobalElementId, GlyphId, Hsla, ImageData, InputEvent,
- IsZero, KeyListener, KeyMatch, KeyMatcher, Keystroke, LayoutId, Model, ModelContext, Modifiers,
- MonochromeSprite, MouseButton, MouseDownEvent, MouseMoveEvent, MouseUpEvent, Path, Pixels,
- PlatformAtlas, PlatformDisplay, PlatformInputHandler, PlatformWindow, Point, PolychromeSprite,
- PromptLevel, Quad, Render, RenderGlyphParams, RenderImageParams, RenderSvgParams, ScaledPixels,
- SceneBuilder, Shadow, SharedString, Size, Style, SubscriberSet, Subscription,
- TaffyLayoutEngine, Task, Underline, UnderlineStyle, View, VisualContext, WeakView,
- WindowBounds, WindowOptions, SUBPIXEL_VARIANTS,
+ DevicePixels, DispatchNodeId, DispatchTree, DisplayId, Edges, Effect, Entity, EntityId,
+ EventEmitter, FileDropEvent, FocusEvent, FontId, GlobalElementId, GlyphId, Hsla, ImageData,
+ InputEvent, IsZero, KeyBinding, KeyContext, KeyDownEvent, LayoutId, Model, ModelContext,
+ Modifiers, MonochromeSprite, MouseButton, MouseDownEvent, MouseMoveEvent, MouseUpEvent, Path,
+ Pixels, PlatformAtlas, PlatformDisplay, PlatformInputHandler, PlatformWindow, Point,
+ PolychromeSprite, PromptLevel, Quad, Render, RenderGlyphParams, RenderImageParams,
+ RenderSvgParams, ScaledPixels, SceneBuilder, Shadow, SharedString, Size, Style, SubscriberSet,
+ Subscription, TaffyLayoutEngine, Task, Underline, UnderlineStyle, View, VisualContext,
+ WeakView, WindowBounds, WindowOptions, SUBPIXEL_VARIANTS,
};
use anyhow::{anyhow, Context as _, Result};
use collections::HashMap;
@@ -60,16 +60,7 @@ pub enum DispatchPhase {
}
type AnyObserver = Box<dyn FnMut(&mut WindowContext) -> bool + 'static>;
-type AnyListener = Box<dyn FnMut(&dyn Any, DispatchPhase, &mut WindowContext) + 'static>;
-type AnyKeyListener = Box<
- dyn Fn(
- &dyn Any,
- &[&DispatchContext],
- DispatchPhase,
- &mut WindowContext,
- ) -> Option<Box<dyn Action>>
- + 'static,
->;
+type AnyMouseListener = Box<dyn FnMut(&dyn Any, DispatchPhase, &mut WindowContext) + 'static>;
type AnyFocusListener = Box<dyn Fn(&FocusEvent, &mut WindowContext) + 'static>;
type AnyWindowFocusListener = Box<dyn FnMut(&FocusEvent, &mut WindowContext) -> bool + 'static>;
@@ -97,20 +88,10 @@ impl FocusId {
/// Obtains whether this handle contains the given handle in the most recently rendered frame.
pub(crate) fn contains(&self, other: Self, cx: &WindowContext) -> bool {
- let mut ancestor = Some(other);
- while let Some(ancestor_id) = ancestor {
- if *self == ancestor_id {
- return true;
- } else {
- ancestor = cx
- .window
- .current_frame
- .focus_parents_by_child
- .get(&ancestor_id)
- .copied();
- }
- }
- false
+ cx.window
+ .current_frame
+ .dispatch_tree
+ .focus_contains(*self, other)
}
}
@@ -227,20 +208,31 @@ pub struct Window {
pub(crate) focus: Option<FocusId>,
}
-#[derive(Default)]
+// #[derive(Default)]
pub(crate) struct Frame {
element_states: HashMap<GlobalElementId, AnyBox>,
- key_matchers: HashMap<GlobalElementId, KeyMatcher>,
- mouse_listeners: HashMap<TypeId, Vec<(StackingOrder, AnyListener)>>,
+ mouse_listeners: HashMap<TypeId, Vec<(StackingOrder, AnyMouseListener)>>,
+ pub(crate) dispatch_tree: DispatchTree,
pub(crate) focus_listeners: Vec<AnyFocusListener>,
- pub(crate) key_dispatch_stack: Vec<KeyDispatchStackFrame>,
- freeze_key_dispatch_stack: bool,
- focus_parents_by_child: HashMap<FocusId, FocusId>,
pub(crate) scene_builder: SceneBuilder,
z_index_stack: StackingOrder,
content_mask_stack: Vec<ContentMask<Pixels>>,
element_offset_stack: Vec<Point<Pixels>>,
- focus_stack: Vec<FocusId>,
+}
+
+impl Frame {
+ pub fn new(dispatch_tree: DispatchTree) -> Self {
+ Frame {
+ element_states: HashMap::default(),
+ mouse_listeners: HashMap::default(),
+ dispatch_tree,
+ focus_listeners: Vec::new(),
+ scene_builder: SceneBuilder::default(),
+ z_index_stack: StackingOrder::default(),
+ content_mask_stack: Vec::new(),
+ element_offset_stack: Vec::new(),
+ }
+ }
}
impl Window {
@@ -309,8 +301,8 @@ impl Window {
layout_engine: TaffyLayoutEngine::new(),
root_view: None,
element_id_stack: GlobalElementId::default(),
- previous_frame: Frame::default(),
- current_frame: Frame::default(),
+ previous_frame: Frame::new(DispatchTree::new(cx.keymap.clone())),
+ current_frame: Frame::new(DispatchTree::new(cx.keymap.clone())),
focus_handles: Arc::new(RwLock::new(SlotMap::with_key())),
focus_listeners: SubscriberSet::new(),
default_prevented: true,
@@ -328,18 +320,6 @@ impl Window {
}
}
-/// When constructing the element tree, we maintain a stack of key dispatch frames until we
-/// find the focused element. We interleave key listeners with dispatch contexts so we can use the
-/// contexts when matching key events against the keymap. A key listener can be either an action
-/// handler or a [KeyDown] / [KeyUp] event listener.
-pub(crate) enum KeyDispatchStackFrame {
- Listener {
- event_type: TypeId,
- listener: AnyKeyListener,
- },
- Context(DispatchContext),
-}
-
/// Indicates which region of the window is visible. Content falling outside of this mask will not be
/// rendered. Currently, only rectangular content masks are supported, but we give the mask its own type
/// to leave room to support more complex shapes in the future.
@@ -407,21 +387,16 @@ impl<'a> WindowContext<'a> {
/// Move focus to the element associated with the given `FocusHandle`.
pub fn focus(&mut self, handle: &FocusHandle) {
- if self.window.focus == Some(handle.id) {
- return;
- }
+ let focus_id = handle.id;
if self.window.last_blur.is_none() {
self.window.last_blur = Some(self.window.focus);
}
- self.window.focus = Some(handle.id);
-
- // self.window.current_frame.key_dispatch_stack.clear()
- // self.window.root_view.initialize()
+ self.window.focus = Some(focus_id);
self.app.push_effect(Effect::FocusChanged {
window_handle: self.window.handle,
- focused: Some(handle.id),
+ focused: Some(focus_id),
});
self.notify();
}
@@ -441,11 +416,18 @@ impl<'a> WindowContext<'a> {
}
pub fn dispatch_action(&mut self, action: Box<dyn Action>) {
- self.defer(|cx| {
- cx.app.propagate_event = true;
- let stack = cx.dispatch_stack();
- cx.dispatch_action_internal(action, &stack[..])
- })
+ if let Some(focus_handle) = self.focused() {
+ self.defer(move |cx| {
+ if let Some(node_id) = cx
+ .window
+ .current_frame
+ .dispatch_tree
+ .focusable_node_id(focus_handle.id)
+ {
+ cx.dispatch_action_on_node(node_id, action);
+ }
+ })
+ }
}
/// Schedules the given function to be run at the end of the current effect cycle, allowing entities
@@ -731,6 +713,43 @@ impl<'a> WindowContext<'a> {
))
}
+ /// Register a key event listener on the window for the current frame. The type of event
+ /// is determined by the first parameter of the given listener. When the next frame is rendered
+ /// the listener will be cleared.
+ ///
+ /// This is a fairly low-level method, so prefer using event handlers on elements unless you have
+ /// a specific need to register a global listener.
+ pub fn on_key_event<Event: 'static>(
+ &mut self,
+ handler: impl Fn(&Event, DispatchPhase, &mut WindowContext) + 'static,
+ ) {
+ self.window
+ .current_frame
+ .dispatch_tree
+ .on_key_event(Rc::new(move |event, phase, cx| {
+ if let Some(event) = event.downcast_ref::<Event>() {
+ handler(event, phase, cx)
+ }
+ }));
+ }
+
+ /// Register an action listener on the window for the current frame. The type of action
+ /// is determined by the first parameter of the given listener. When the next frame is rendered
+ /// the listener will be cleared.
+ ///
+ /// This is a fairly low-level method, so prefer using action handlers on elements unless you have
+ /// a specific need to register a global listener.
+ pub fn on_action(
+ &mut self,
+ action_type: TypeId,
+ handler: impl Fn(&dyn Any, DispatchPhase, &mut WindowContext) + 'static,
+ ) {
+ self.window.current_frame.dispatch_tree.on_action(
+ action_type,
+ Rc::new(move |action, phase, cx| handler(action, phase, cx)),
+ );
+ }
+
/// The position of the mouse relative to the window.
pub fn mouse_position(&self) -> Point<Pixels> {
self.window.mouse_position
@@ -1079,26 +1098,6 @@ impl<'a> WindowContext<'a> {
self.window.dirty = false;
}
- pub(crate) fn dispatch_stack(&mut self) -> Vec<KeyDispatchStackFrame> {
- let root_view = self.window.root_view.take().unwrap();
- let window = &mut *self.window;
- let mut spare_frame = Frame::default();
- mem::swap(&mut spare_frame, &mut window.previous_frame);
-
- self.start_frame();
-
- root_view.draw_dispatch_stack(self);
-
- let window = &mut *self.window;
- // restore the old values of current and previous frame,
- // putting the new frame into spare_frame.
- mem::swap(&mut window.current_frame, &mut window.previous_frame);
- mem::swap(&mut spare_frame, &mut window.previous_frame);
- self.window.root_view = Some(root_view);
-
- spare_frame.key_dispatch_stack
- }
-
/// Rotate the current frame and the previous frame, then clear the current frame.
/// We repopulate all state in the current frame during each paint.
fn start_frame(&mut self) {
@@ -1110,12 +1109,9 @@ impl<'a> WindowContext<'a> {
mem::swap(&mut window.previous_frame, &mut window.current_frame);
let frame = &mut window.current_frame;
frame.element_states.clear();
- frame.key_matchers.clear();
frame.mouse_listeners.values_mut().for_each(Vec::clear);
frame.focus_listeners.clear();
- frame.key_dispatch_stack.clear();
- frame.focus_parents_by_child.clear();
- frame.freeze_key_dispatch_stack = false;
+ frame.dispatch_tree.clear();
}
/// Dispatch a mouse or keyboard event on the window.
@@ -1177,146 +1173,172 @@ impl<'a> WindowContext<'a> {
};
if let Some(any_mouse_event) = event.mouse_event() {
- if let Some(mut handlers) = self
- .window
- .current_frame
- .mouse_listeners
- .remove(&any_mouse_event.type_id())
- {
- // Because handlers may add other handlers, we sort every time.
- handlers.sort_by(|(a, _), (b, _)| a.cmp(b));
+ self.dispatch_mouse_event(any_mouse_event);
+ } else if let Some(any_key_event) = event.keyboard_event() {
+ self.dispatch_key_event(any_key_event);
+ }
- // Capture phase, events bubble from back to front. Handlers for this phase are used for
- // special purposes, such as detecting events outside of a given Bounds.
- for (_, handler) in &mut handlers {
- handler(any_mouse_event, DispatchPhase::Capture, self);
- if !self.app.propagate_event {
- break;
- }
+ !self.app.propagate_event
+ }
+
+ fn dispatch_mouse_event(&mut self, event: &dyn Any) {
+ if let Some(mut handlers) = self
+ .window
+ .current_frame
+ .mouse_listeners
+ .remove(&event.type_id())
+ {
+ // Because handlers may add other handlers, we sort every time.
+ handlers.sort_by(|(a, _), (b, _)| a.cmp(b));
+
+ // Capture phase, events bubble from back to front. Handlers for this phase are used for
+ // special purposes, such as detecting events outside of a given Bounds.
+ for (_, handler) in &mut handlers {
+ handler(event, DispatchPhase::Capture, self);
+ if !self.app.propagate_event {
+ break;
}
+ }
- // Bubble phase, where most normal handlers do their work.
- if self.app.propagate_event {
- for (_, handler) in handlers.iter_mut().rev() {
- handler(any_mouse_event, DispatchPhase::Bubble, self);
- if !self.app.propagate_event {
- break;
- }
+ // Bubble phase, where most normal handlers do their work.
+ if self.app.propagate_event {
+ for (_, handler) in handlers.iter_mut().rev() {
+ handler(event, DispatchPhase::Bubble, self);
+ if !self.app.propagate_event {
+ break;
}
}
+ }
- if self.app.propagate_event
- && any_mouse_event.downcast_ref::<MouseUpEvent>().is_some()
- {
- self.active_drag = None;
- }
+ if self.app.propagate_event && event.downcast_ref::<MouseUpEvent>().is_some() {
+ self.active_drag = None;
+ }
- // Just in case any handlers added new handlers, which is weird, but possible.
- handlers.extend(
- self.window
- .current_frame
- .mouse_listeners
- .get_mut(&any_mouse_event.type_id())
- .into_iter()
- .flat_map(|handlers| handlers.drain(..)),
- );
+ // Just in case any handlers added new handlers, which is weird, but possible.
+ handlers.extend(
self.window
.current_frame
.mouse_listeners
- .insert(any_mouse_event.type_id(), handlers);
- }
- } else if let Some(any_key_event) = event.keyboard_event() {
- let key_dispatch_stack = mem::take(&mut self.window.current_frame.key_dispatch_stack);
- let key_event_type = any_key_event.type_id();
- let mut context_stack = SmallVec::<[&DispatchContext; 16]>::new();
-
- for (ix, frame) in key_dispatch_stack.iter().enumerate() {
- match frame {
- KeyDispatchStackFrame::Listener {
- event_type,
- listener,
- } => {
- if key_event_type == *event_type {
- if let Some(action) = listener(
- any_key_event,
- &context_stack,
- DispatchPhase::Capture,
- self,
- ) {
- self.dispatch_action_internal(action, &key_dispatch_stack[..ix]);
- }
- if !self.app.propagate_event {
- break;
- }
- }
- }
- KeyDispatchStackFrame::Context(context) => {
- context_stack.push(&context);
+ .get_mut(&event.type_id())
+ .into_iter()
+ .flat_map(|handlers| handlers.drain(..)),
+ );
+ self.window
+ .current_frame
+ .mouse_listeners
+ .insert(event.type_id(), handlers);
+ }
+ }
+
+ fn dispatch_key_event(&mut self, event: &dyn Any) {
+ if let Some(node_id) = self.window.focus.and_then(|focus_id| {
+ self.window
+ .current_frame
+ .dispatch_tree
+ .focusable_node_id(focus_id)
+ }) {
+ let dispatch_path = self
+ .window
+ .current_frame
+ .dispatch_tree
+ .dispatch_path(node_id);
+
+ // Capture phase
+ let mut context_stack: SmallVec<[KeyContext; 16]> = SmallVec::new();
+ self.propagate_event = true;
+
+ for node_id in &dispatch_path {
+ let node = self.window.current_frame.dispatch_tree.node(*node_id);
+
+ if !node.context.is_empty() {
+ context_stack.push(node.context.clone());
+ }
+
+ for key_listener in node.key_listeners.clone() {
+ key_listener(event, DispatchPhase::Capture, self);
+ if !self.propagate_event {
+ return;
}
}
}
- if self.app.propagate_event {
- for (ix, frame) in key_dispatch_stack.iter().enumerate().rev() {
- match frame {
- KeyDispatchStackFrame::Listener {
- event_type,
- listener,
- } => {
- if key_event_type == *event_type {
- if let Some(action) = listener(
- any_key_event,
- &context_stack,
- DispatchPhase::Bubble,
- self,
- ) {
- self.dispatch_action_internal(
- action,
- &key_dispatch_stack[..ix],
- );
- }
-
- if !self.app.propagate_event {
- break;
- }
+ // Bubble phase
+ for node_id in dispatch_path.iter().rev() {
+ // Handle low level key events
+ let node = self.window.current_frame.dispatch_tree.node(*node_id);
+ for key_listener in node.key_listeners.clone() {
+ key_listener(event, DispatchPhase::Bubble, self);
+ if !self.propagate_event {
+ return;
+ }
+ }
+
+ // Match keystrokes
+ let node = self.window.current_frame.dispatch_tree.node(*node_id);
+ if !node.context.is_empty() {
+ if let Some(key_down_event) = event.downcast_ref::<KeyDownEvent>() {
+ if let Some(action) = self
+ .window
+ .current_frame
+ .dispatch_tree
+ .dispatch_key(&key_down_event.keystroke, &context_stack)
+ {
+ self.dispatch_action_on_node(*node_id, action);
+ if !self.propagate_event {
+ return;
}
}
- KeyDispatchStackFrame::Context(_) => {
- context_stack.pop();
- }
}
+
+ context_stack.pop();
}
}
-
- drop(context_stack);
- self.window.current_frame.key_dispatch_stack = key_dispatch_stack;
}
-
- !self.app.propagate_event
}
- /// Attempt to map a keystroke to an action based on the keymap.
- pub fn match_keystroke(
- &mut self,
- element_id: &GlobalElementId,
- keystroke: &Keystroke,
- context_stack: &[&DispatchContext],
- ) -> KeyMatch {
- let key_match = self
+ fn dispatch_action_on_node(&mut self, node_id: DispatchNodeId, action: Box<dyn Action>) {
+ let dispatch_path = self
.window
.current_frame
- .key_matchers
- .get_mut(element_id)
- .unwrap()
- .match_keystroke(keystroke, context_stack);
-
- if key_match.is_some() {
- for matcher in self.window.current_frame.key_matchers.values_mut() {
- matcher.clear_pending();
+ .dispatch_tree
+ .dispatch_path(node_id);
+
+ // Capture phase
+ for node_id in &dispatch_path {
+ let node = self.window.current_frame.dispatch_tree.node(*node_id);
+ for DispatchActionListener {
+ action_type,
+ listener,
+ } in node.action_listeners.clone()
+ {
+ let any_action = action.as_any();
+ if action_type == any_action.type_id() {
+ listener(any_action, DispatchPhase::Capture, self);
+ if !self.propagate_event {
+ return;
+ }
+ }
}
}
- key_match
+ // Bubble phase
+ for node_id in dispatch_path.iter().rev() {
+ let node = self.window.current_frame.dispatch_tree.node(*node_id);
+ for DispatchActionListener {
+ action_type,
+ listener,
+ } in node.action_listeners.clone()
+ {
+ let any_action = action.as_any();
+ if action_type == any_action.type_id() {
+ self.propagate_event = false; // Actions stop propagation by default during the bubble phase
+ listener(any_action, DispatchPhase::Bubble, self);
+ if !self.propagate_event {
+ return;
+ }
+ }
+ }
+ }
}
/// Register the given handler to be invoked whenever the global of the given type
@@ -1345,106 +1367,22 @@ impl<'a> WindowContext<'a> {
self.window.platform_window.prompt(level, msg, answers)
}
- pub fn available_actions(&self) -> impl Iterator<Item = Box<dyn Action>> + '_ {
- let key_dispatch_stack = &self.window.previous_frame.key_dispatch_stack;
- key_dispatch_stack.iter().filter_map(|frame| {
- match frame {
- // todo!factor out a KeyDispatchStackFrame::Action
- KeyDispatchStackFrame::Listener {
- event_type,
- listener: _,
- } => {
- match build_action_from_type(event_type) {
- Ok(action) => Some(action),
- Err(err) => {
- dbg!(err);
- None
- } // we'll hit his if TypeId == KeyDown
- }
- }
- KeyDispatchStackFrame::Context(_) => None,
- }
- })
- }
-
- pub(crate) fn dispatch_action_internal(
- &mut self,
- action: Box<dyn Action>,
- dispatch_stack: &[KeyDispatchStackFrame],
- ) {
- let action_type = action.as_any().type_id();
-
- if let Some(mut global_listeners) = self.app.global_action_listeners.remove(&action_type) {
- for listener in &global_listeners {
- listener(action.as_ref(), DispatchPhase::Capture, self);
- if !self.app.propagate_event {
- break;
- }
- }
- global_listeners.extend(
- self.global_action_listeners
- .remove(&action_type)
- .unwrap_or_default(),
- );
- self.global_action_listeners
- .insert(action_type, global_listeners);
- }
-
- if self.app.propagate_event {
- for stack_frame in dispatch_stack {
- if let KeyDispatchStackFrame::Listener {
- event_type,
- listener,
- } = stack_frame
- {
- if action_type == *event_type {
- listener(action.as_any(), &[], DispatchPhase::Capture, self);
- if !self.app.propagate_event {
- break;
- }
- }
- }
- }
- }
-
- if self.app.propagate_event {
- for stack_frame in dispatch_stack.iter().rev() {
- if let KeyDispatchStackFrame::Listener {
- event_type,
- listener,
- } = stack_frame
- {
- if action_type == *event_type {
- self.app.propagate_event = false;
- listener(action.as_any(), &[], DispatchPhase::Bubble, self);
- if !self.app.propagate_event {
- break;
- }
- }
- }
- }
+ pub fn available_actions(&self) -> Vec<Box<dyn Action>> {
+ if let Some(focus_id) = self.window.focus {
+ self.window
+ .current_frame
+ .dispatch_tree
+ .available_actions(focus_id)
+ } else {
+ Vec::new()
}
+ }
- if self.app.propagate_event {
- if let Some(mut global_listeners) =
- self.app.global_action_listeners.remove(&action_type)
- {
- for listener in global_listeners.iter().rev() {
- self.app.propagate_event = false;
- listener(action.as_ref(), DispatchPhase::Bubble, self);
- if !self.app.propagate_event {
- break;
- }
- }
- global_listeners.extend(
- self.global_action_listeners
- .remove(&action_type)
- .unwrap_or_default(),
- );
- self.global_action_listeners
- .insert(action_type, global_listeners);
- }
- }
+ pub fn bindings_for_action(&self, action: &dyn Action) -> Vec<KeyBinding> {
+ self.window
+ .current_frame
+ .dispatch_tree
+ .bindings_for_action(action)
}
}
@@ -1631,22 +1569,9 @@ pub trait BorrowWindow: BorrowMut<Window> + BorrowMut<AppContext> {
id: impl Into<ElementId>,
f: impl FnOnce(GlobalElementId, &mut Self) -> R,
) -> R {
- let keymap = self.app_mut().keymap.clone();
let window = self.window_mut();
window.element_id_stack.push(id.into());
let global_id = window.element_id_stack.clone();
-
- if window.current_frame.key_matchers.get(&global_id).is_none() {
- window.current_frame.key_matchers.insert(
- global_id.clone(),
- window
- .previous_frame
- .key_matchers
- .remove(&global_id)
- .unwrap_or_else(|| KeyMatcher::new(keymap)),
- );
- }
-
let result = f(global_id, self);
let window: &mut Window = self.borrow_mut();
window.element_id_stack.pop();
@@ -2134,94 +2059,28 @@ impl<'a, V: 'static> ViewContext<'a, V> {
}));
}
- pub fn with_key_listeners<R>(
- &mut self,
- key_listeners: impl IntoIterator<Item = (TypeId, KeyListener<V>)>,
- f: impl FnOnce(&mut Self) -> R,
- ) -> R {
- let old_stack_len = self.window.current_frame.key_dispatch_stack.len();
- if !self.window.current_frame.freeze_key_dispatch_stack {
- for (event_type, listener) in key_listeners {
- let handle = self.view().downgrade();
- let listener = Box::new(
- move |event: &dyn Any,
- context_stack: &[&DispatchContext],
- phase: DispatchPhase,
- cx: &mut WindowContext<'_>| {
- handle
- .update(cx, |view, cx| {
- listener(view, event, context_stack, phase, cx)
- })
- .log_err()
- .flatten()
- },
- );
- self.window.current_frame.key_dispatch_stack.push(
- KeyDispatchStackFrame::Listener {
- event_type,
- listener,
- },
- );
- }
- }
-
- let result = f(self);
-
- if !self.window.current_frame.freeze_key_dispatch_stack {
- self.window
- .current_frame
- .key_dispatch_stack
- .truncate(old_stack_len);
- }
-
- result
- }
-
- pub fn with_key_dispatch_context<R>(
+ pub fn with_key_dispatch<R>(
&mut self,
- context: DispatchContext,
- f: impl FnOnce(&mut Self) -> R,
+ context: KeyContext,
+ focus_handle: Option<FocusHandle>,
+ f: impl FnOnce(Option<FocusHandle>, &mut Self) -> R,
) -> R {
- if context.is_empty() {
- return f(self);
- }
-
- if !self.window.current_frame.freeze_key_dispatch_stack {
- self.window
- .current_frame
- .key_dispatch_stack
- .push(KeyDispatchStackFrame::Context(context));
- }
-
- let result = f(self);
+ let window = &mut self.window;
- if !self.window.previous_frame.freeze_key_dispatch_stack {
- self.window.previous_frame.key_dispatch_stack.pop();
- }
-
- result
- }
-
- pub fn with_focus<R>(
- &mut self,
- focus_handle: FocusHandle,
- f: impl FnOnce(&mut Self) -> R,
- ) -> R {
- if let Some(parent_focus_id) = self.window.current_frame.focus_stack.last().copied() {
- self.window
+ window
+ .current_frame
+ .dispatch_tree
+ .push_node(context, &mut window.previous_frame.dispatch_tree);
+ if let Some(focus_handle) = focus_handle.as_ref() {
+ window
.current_frame
- .focus_parents_by_child
- .insert(focus_handle.id, parent_focus_id);
+ .dispatch_tree
+ .make_focusable(focus_handle.id);
}
- self.window.current_frame.focus_stack.push(focus_handle.id);
+ let result = f(focus_handle, self);
- if Some(focus_handle.id) == self.window.focus {
- self.window.current_frame.freeze_key_dispatch_stack = true;
- }
+ self.window.current_frame.dispatch_tree.pop_node();
- let result = f(self);
-
- self.window.current_frame.focus_stack.pop();
result
}
@@ -2275,6 +2134,32 @@ impl<'a, V: 'static> ViewContext<'a, V> {
});
}
+ pub fn on_key_event<Event: 'static>(
+ &mut self,
+ handler: impl Fn(&mut V, &Event, DispatchPhase, &mut ViewContext<V>) + 'static,
+ ) {
+ let handle = self.view();
+ self.window_cx.on_key_event(move |event, phase, cx| {
+ handle.update(cx, |view, cx| {
+ handler(view, event, phase, cx);
+ })
+ });
+ }
+
+ pub fn on_action(
+ &mut self,
+ action_type: TypeId,
+ handler: impl Fn(&mut V, &dyn Any, DispatchPhase, &mut ViewContext<V>) + 'static,
+ ) {
+ let handle = self.view();
+ self.window_cx
+ .on_action(action_type, move |action, phase, cx| {
+ handle.update(cx, |view, cx| {
+ handler(view, action, phase, cx);
+ })
+ });
+ }
+
/// Set an input handler, such as [ElementInputHandler], which interfaces with the
/// platform to receive textual input with proper integration with concerns such
/// as IME interactions.
@@ -1,11 +1,10 @@
use editor::Editor;
use gpui::{
- div, uniform_list, Component, Div, FocusEnabled, ParentElement, Render, StatefulInteractivity,
- StatelessInteractive, Styled, Task, UniformListScrollHandle, View, ViewContext, VisualContext,
- WindowContext,
+ div, uniform_list, Component, Div, ParentElement, Render, StatelessInteractive, Styled, Task,
+ UniformListScrollHandle, View, ViewContext, VisualContext, WindowContext,
};
-use std::cmp;
-use ui::{prelude::*, v_stack, Divider};
+use std::{cmp, sync::Arc};
+use ui::{prelude::*, v_stack, Divider, Label, LabelColor};
pub struct Picker<D: PickerDelegate> {
pub delegate: D,
@@ -21,7 +20,7 @@ pub trait PickerDelegate: Sized + 'static {
fn selected_index(&self) -> usize;
fn set_selected_index(&mut self, ix: usize, cx: &mut ViewContext<Picker<Self>>);
- // fn placeholder_text(&self) -> Arc<str>;
+ fn placeholder_text(&self) -> Arc<str>;
fn update_matches(&mut self, query: String, cx: &mut ViewContext<Picker<Self>>) -> Task<()>;
fn confirm(&mut self, secondary: bool, cx: &mut ViewContext<Picker<Self>>);
@@ -37,7 +36,11 @@ pub trait PickerDelegate: Sized + 'static {
impl<D: PickerDelegate> Picker<D> {
pub fn new(delegate: D, cx: &mut ViewContext<Self>) -> Self {
- let editor = cx.build_view(|cx| Editor::single_line(cx));
+ let editor = cx.build_view(|cx| {
+ let mut editor = Editor::single_line(cx);
+ editor.set_placeholder_text(delegate.placeholder_text(), cx);
+ editor
+ });
cx.subscribe(&editor, Self::on_input_editor_event).detach();
Self {
delegate,
@@ -136,13 +139,11 @@ impl<D: PickerDelegate> Picker<D> {
}
impl<D: PickerDelegate> Render for Picker<D> {
- type Element = Div<Self, StatefulInteractivity<Self>, FocusEnabled<Self>>;
+ type Element = Div<Self>;
fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
div()
.context("picker")
- .id("picker-container")
- .focusable()
.size_full()
.elevation_2(cx)
.on_action(Self::select_next)
@@ -159,23 +160,35 @@ impl<D: PickerDelegate> Render for Picker<D> {
.child(div().px_1().py_0p5().child(self.editor.clone())),
)
.child(Divider::horizontal())
- .child(
- v_stack()
- .p_1()
- .grow()
- .child(
- uniform_list("candidates", self.delegate.match_count(), {
- move |this: &mut Self, visible_range, cx| {
- let selected_ix = this.delegate.selected_index();
- visible_range
- .map(|ix| this.delegate.render_match(ix, ix == selected_ix, cx))
- .collect()
- }
- })
- .track_scroll(self.scroll_handle.clone()),
- )
- .max_h_72()
- .overflow_hidden(),
- )
+ .when(self.delegate.match_count() > 0, |el| {
+ el.child(
+ v_stack()
+ .p_1()
+ .grow()
+ .child(
+ uniform_list("candidates", self.delegate.match_count(), {
+ move |this: &mut Self, visible_range, cx| {
+ let selected_ix = this.delegate.selected_index();
+ visible_range
+ .map(|ix| {
+ this.delegate.render_match(ix, ix == selected_ix, cx)
+ })
+ .collect()
+ }
+ })
+ .track_scroll(self.scroll_handle.clone()),
+ )
+ .max_h_72()
+ .overflow_hidden(),
+ )
+ })
+ .when(self.delegate.match_count() == 0, |el| {
+ el.child(
+ v_stack()
+ .p_1()
+ .grow()
+ .child(Label::new("No matches").color(LabelColor::Muted)),
+ )
+ })
}
}
@@ -1,12 +1,16 @@
use gpui::{
- actions, div, Div, FocusEnabled, Focusable, KeyBinding, ParentElement, Render,
- StatefulInteractivity, StatelessInteractive, Styled, View, VisualContext, WindowContext,
+ actions, div, Div, FocusHandle, Focusable, FocusableKeyDispatch, KeyBinding, ParentElement,
+ Render, StatefulInteractivity, StatelessInteractive, Styled, View, VisualContext,
+ WindowContext,
};
use theme2::ActiveTheme;
actions!(ActionA, ActionB, ActionC);
-pub struct FocusStory {}
+pub struct FocusStory {
+ child_1_focus: FocusHandle,
+ child_2_focus: FocusHandle,
+}
impl FocusStory {
pub fn view(cx: &mut WindowContext) -> View<Self> {
@@ -16,12 +20,15 @@ impl FocusStory {
KeyBinding::new("cmd-c", ActionC, None),
]);
- cx.build_view(move |cx| Self {})
+ cx.build_view(move |cx| Self {
+ child_1_focus: cx.focus_handle(),
+ child_2_focus: cx.focus_handle(),
+ })
}
}
impl Render for FocusStory {
- type Element = Div<Self, StatefulInteractivity<Self>, FocusEnabled<Self>>;
+ type Element = Div<Self, StatefulInteractivity<Self>, FocusableKeyDispatch<Self>>;
fn render(&mut self, cx: &mut gpui::ViewContext<Self>) -> Self::Element {
let theme = cx.theme();
@@ -31,18 +38,16 @@ impl Render for FocusStory {
let color_4 = theme.status().conflict;
let color_5 = theme.status().ignored;
let color_6 = theme.status().renamed;
- let child_1 = cx.focus_handle();
- let child_2 = cx.focus_handle();
div()
.id("parent")
.focusable()
.context("parent")
.on_action(|_, action: &ActionA, cx| {
- println!("Action A dispatched on parent during");
+ println!("Action A dispatched on parent");
})
.on_action(|_, action: &ActionB, cx| {
- println!("Action B dispatched on parent during");
+ println!("Action B dispatched on parent");
})
.on_focus(|_, _, _| println!("Parent focused"))
.on_blur(|_, _, _| println!("Parent blurred"))
@@ -56,7 +61,7 @@ impl Render for FocusStory {
.focus_in(|style| style.bg(color_3))
.child(
div()
- .track_focus(&child_1)
+ .track_focus(&self.child_1_focus)
.context("child-1")
.on_action(|_, action: &ActionB, cx| {
println!("Action B dispatched on child 1 during");
@@ -76,10 +81,10 @@ impl Render for FocusStory {
)
.child(
div()
- .track_focus(&child_2)
+ .track_focus(&self.child_2_focus)
.context("child-2")
.on_action(|_, action: &ActionC, cx| {
- println!("Action C dispatched on child 2 during");
+ println!("Action C dispatched on child 2");
})
.w_full()
.h_6()
@@ -44,6 +44,10 @@ impl PickerDelegate for Delegate {
self.candidates.len()
}
+ fn placeholder_text(&self) -> Arc<str> {
+ "Test".into()
+ }
+
fn render_match(
&self,
ix: usize,
@@ -48,7 +48,7 @@ fn main() {
let args = Args::parse();
let story_selector = args.story.clone();
- let theme_name = args.theme.unwrap_or("Zed Pro Moonlight".to_string());
+ let theme_name = args.theme.unwrap_or("One Dark".to_string());
let asset_source = Arc::new(Assets);
gpui::App::production(asset_source).run(move |cx| {
@@ -1,261 +1,15 @@
-use gpui::{hsla, Hsla, Rgba};
+use gpui::{Hsla, Rgba};
-use crate::colors::{StatusColors, SystemColors, ThemeColors};
use crate::scale::{ColorScaleSet, ColorScales};
-use crate::syntax::SyntaxTheme;
-use crate::{ColorScale, PlayerColor, PlayerColors};
+use crate::ColorScale;
+use crate::{SystemColors, ThemeColors};
-impl Default for PlayerColors {
- fn default() -> Self {
- Self(vec![
- PlayerColor {
- cursor: blue().dark().step_9(),
- background: blue().dark().step_5(),
- selection: blue().dark().step_3(),
- },
- PlayerColor {
- cursor: orange().dark().step_9(),
- background: orange().dark().step_5(),
- selection: orange().dark().step_3(),
- },
- PlayerColor {
- cursor: pink().dark().step_9(),
- background: pink().dark().step_5(),
- selection: pink().dark().step_3(),
- },
- PlayerColor {
- cursor: lime().dark().step_9(),
- background: lime().dark().step_5(),
- selection: lime().dark().step_3(),
- },
- PlayerColor {
- cursor: purple().dark().step_9(),
- background: purple().dark().step_5(),
- selection: purple().dark().step_3(),
- },
- PlayerColor {
- cursor: amber().dark().step_9(),
- background: amber().dark().step_5(),
- selection: amber().dark().step_3(),
- },
- PlayerColor {
- cursor: jade().dark().step_9(),
- background: jade().dark().step_5(),
- selection: jade().dark().step_3(),
- },
- PlayerColor {
- cursor: red().dark().step_9(),
- background: red().dark().step_5(),
- selection: red().dark().step_3(),
- },
- ])
- }
-}
-
-impl PlayerColors {
- pub fn default_light() -> Self {
- Self(vec![
- PlayerColor {
- cursor: blue().light().step_9(),
- background: blue().light().step_4(),
- selection: blue().light().step_3(),
- },
- PlayerColor {
- cursor: orange().light().step_9(),
- background: orange().light().step_4(),
- selection: orange().light().step_3(),
- },
- PlayerColor {
- cursor: pink().light().step_9(),
- background: pink().light().step_4(),
- selection: pink().light().step_3(),
- },
- PlayerColor {
- cursor: lime().light().step_9(),
- background: lime().light().step_4(),
- selection: lime().light().step_3(),
- },
- PlayerColor {
- cursor: purple().light().step_9(),
- background: purple().light().step_4(),
- selection: purple().light().step_3(),
- },
- PlayerColor {
- cursor: amber().light().step_9(),
- background: amber().light().step_4(),
- selection: amber().light().step_3(),
- },
- PlayerColor {
- cursor: jade().light().step_9(),
- background: jade().light().step_4(),
- selection: jade().light().step_3(),
- },
- PlayerColor {
- cursor: red().light().step_9(),
- background: red().light().step_4(),
- selection: red().light().step_3(),
- },
- ])
- }
-}
-
-fn neutral() -> ColorScaleSet {
+pub(crate) fn neutral() -> ColorScaleSet {
slate()
}
-impl Default for SystemColors {
- fn default() -> Self {
- Self {
- transparent: hsla(0.0, 0.0, 0.0, 0.0),
- mac_os_traffic_light_red: hsla(0.0139, 0.79, 0.65, 1.0),
- mac_os_traffic_light_yellow: hsla(0.114, 0.88, 0.63, 1.0),
- mac_os_traffic_light_green: hsla(0.313, 0.49, 0.55, 1.0),
- }
- }
-}
-
-impl Default for StatusColors {
- fn default() -> Self {
- Self {
- conflict: red().dark().step_9(),
- created: grass().dark().step_9(),
- deleted: red().dark().step_9(),
- error: red().dark().step_9(),
- hidden: neutral().dark().step_9(),
- ignored: neutral().dark().step_9(),
- info: blue().dark().step_9(),
- modified: yellow().dark().step_9(),
- renamed: blue().dark().step_9(),
- success: grass().dark().step_9(),
- warning: yellow().dark().step_9(),
- }
- }
-}
-
-impl SyntaxTheme {
- pub fn default_light() -> Self {
- Self {
- highlights: vec![
- ("attribute".into(), cyan().light().step_11().into()),
- ("boolean".into(), tomato().light().step_11().into()),
- ("comment".into(), neutral().light().step_11().into()),
- ("comment.doc".into(), iris().light().step_12().into()),
- ("constant".into(), red().light().step_9().into()),
- ("constructor".into(), red().light().step_9().into()),
- ("embedded".into(), red().light().step_9().into()),
- ("emphasis".into(), red().light().step_9().into()),
- ("emphasis.strong".into(), red().light().step_9().into()),
- ("enum".into(), red().light().step_9().into()),
- ("function".into(), red().light().step_9().into()),
- ("hint".into(), red().light().step_9().into()),
- ("keyword".into(), orange().light().step_11().into()),
- ("label".into(), red().light().step_9().into()),
- ("link_text".into(), red().light().step_9().into()),
- ("link_uri".into(), red().light().step_9().into()),
- ("number".into(), red().light().step_9().into()),
- ("operator".into(), red().light().step_9().into()),
- ("predictive".into(), red().light().step_9().into()),
- ("preproc".into(), red().light().step_9().into()),
- ("primary".into(), red().light().step_9().into()),
- ("property".into(), red().light().step_9().into()),
- ("punctuation".into(), neutral().light().step_11().into()),
- (
- "punctuation.bracket".into(),
- neutral().light().step_11().into(),
- ),
- (
- "punctuation.delimiter".into(),
- neutral().light().step_11().into(),
- ),
- (
- "punctuation.list_marker".into(),
- blue().light().step_11().into(),
- ),
- ("punctuation.special".into(), red().light().step_9().into()),
- ("string".into(), jade().light().step_11().into()),
- ("string.escape".into(), red().light().step_9().into()),
- ("string.regex".into(), tomato().light().step_11().into()),
- ("string.special".into(), red().light().step_9().into()),
- (
- "string.special.symbol".into(),
- red().light().step_9().into(),
- ),
- ("tag".into(), red().light().step_9().into()),
- ("text.literal".into(), red().light().step_9().into()),
- ("title".into(), red().light().step_9().into()),
- ("type".into(), red().light().step_9().into()),
- ("variable".into(), red().light().step_9().into()),
- ("variable.special".into(), red().light().step_9().into()),
- ("variant".into(), red().light().step_9().into()),
- ],
- inlay_style: tomato().light().step_1().into(), // todo!("nate: use a proper style")
- suggestion_style: orange().light().step_1().into(), // todo!("nate: use proper style")
- }
- }
-
- pub fn default_dark() -> Self {
- Self {
- highlights: vec![
- ("attribute".into(), tomato().dark().step_11().into()),
- ("boolean".into(), tomato().dark().step_11().into()),
- ("comment".into(), neutral().dark().step_11().into()),
- ("comment.doc".into(), iris().dark().step_12().into()),
- ("constant".into(), orange().dark().step_11().into()),
- ("constructor".into(), gold().dark().step_11().into()),
- ("embedded".into(), red().dark().step_11().into()),
- ("emphasis".into(), red().dark().step_11().into()),
- ("emphasis.strong".into(), red().dark().step_11().into()),
- ("enum".into(), yellow().dark().step_11().into()),
- ("function".into(), blue().dark().step_11().into()),
- ("hint".into(), indigo().dark().step_11().into()),
- ("keyword".into(), plum().dark().step_11().into()),
- ("label".into(), red().dark().step_11().into()),
- ("link_text".into(), red().dark().step_11().into()),
- ("link_uri".into(), red().dark().step_11().into()),
- ("number".into(), red().dark().step_11().into()),
- ("operator".into(), red().dark().step_11().into()),
- ("predictive".into(), red().dark().step_11().into()),
- ("preproc".into(), red().dark().step_11().into()),
- ("primary".into(), red().dark().step_11().into()),
- ("property".into(), red().dark().step_11().into()),
- ("punctuation".into(), neutral().dark().step_11().into()),
- (
- "punctuation.bracket".into(),
- neutral().dark().step_11().into(),
- ),
- (
- "punctuation.delimiter".into(),
- neutral().dark().step_11().into(),
- ),
- (
- "punctuation.list_marker".into(),
- blue().dark().step_11().into(),
- ),
- ("punctuation.special".into(), red().dark().step_11().into()),
- ("string".into(), lime().dark().step_11().into()),
- ("string.escape".into(), orange().dark().step_11().into()),
- ("string.regex".into(), tomato().dark().step_11().into()),
- ("string.special".into(), red().dark().step_11().into()),
- (
- "string.special.symbol".into(),
- red().dark().step_11().into(),
- ),
- ("tag".into(), red().dark().step_11().into()),
- ("text.literal".into(), purple().dark().step_11().into()),
- ("title".into(), sky().dark().step_11().into()),
- ("type".into(), mint().dark().step_11().into()),
- ("variable".into(), red().dark().step_11().into()),
- ("variable.special".into(), red().dark().step_11().into()),
- ("variant".into(), red().dark().step_11().into()),
- ],
- inlay_style: neutral().dark().step_11().into(), // todo!("nate: use a proper style")
- suggestion_style: orange().dark().step_11().into(), // todo!("nate: use a proper style")
- }
- }
-}
-
impl ThemeColors {
- pub fn default_light() -> Self {
+ pub fn light() -> Self {
let system = SystemColors::default();
Self {
@@ -327,7 +81,7 @@ impl ThemeColors {
}
}
- pub fn default_dark() -> Self {
+ pub fn dark() -> Self {
let system = SystemColors::default();
Self {
@@ -470,7 +224,7 @@ pub fn default_color_scales() -> ColorScales {
}
}
-fn gray() -> ColorScaleSet {
+pub(crate) fn gray() -> ColorScaleSet {
StaticColorScaleSet {
scale: "Gray",
light: [
@@ -534,7 +288,7 @@ fn gray() -> ColorScaleSet {
.unwrap()
}
-fn mauve() -> ColorScaleSet {
+pub(crate) fn mauve() -> ColorScaleSet {
StaticColorScaleSet {
scale: "Mauve",
light: [
@@ -598,7 +352,7 @@ fn mauve() -> ColorScaleSet {
.unwrap()
}
-fn slate() -> ColorScaleSet {
+pub(crate) fn slate() -> ColorScaleSet {
StaticColorScaleSet {
scale: "Slate",
light: [
@@ -662,7 +416,7 @@ fn slate() -> ColorScaleSet {
.unwrap()
}
-fn sage() -> ColorScaleSet {
+pub(crate) fn sage() -> ColorScaleSet {
StaticColorScaleSet {
scale: "Sage",
light: [
@@ -726,7 +480,7 @@ fn sage() -> ColorScaleSet {
.unwrap()
}
-fn olive() -> ColorScaleSet {
+pub(crate) fn olive() -> ColorScaleSet {
StaticColorScaleSet {
scale: "Olive",
light: [
@@ -790,7 +544,7 @@ fn olive() -> ColorScaleSet {
.unwrap()
}
-fn sand() -> ColorScaleSet {
+pub(crate) fn sand() -> ColorScaleSet {
StaticColorScaleSet {
scale: "Sand",
light: [
@@ -854,7 +608,7 @@ fn sand() -> ColorScaleSet {
.unwrap()
}
-fn gold() -> ColorScaleSet {
+pub(crate) fn gold() -> ColorScaleSet {
StaticColorScaleSet {
scale: "Gold",
light: [
@@ -918,7 +672,7 @@ fn gold() -> ColorScaleSet {
.unwrap()
}
-fn bronze() -> ColorScaleSet {
+pub(crate) fn bronze() -> ColorScaleSet {
StaticColorScaleSet {
scale: "Bronze",
light: [
@@ -982,7 +736,7 @@ fn bronze() -> ColorScaleSet {
.unwrap()
}
-fn brown() -> ColorScaleSet {
+pub(crate) fn brown() -> ColorScaleSet {
StaticColorScaleSet {
scale: "Brown",
light: [
@@ -1046,7 +800,7 @@ fn brown() -> ColorScaleSet {
.unwrap()
}
-fn yellow() -> ColorScaleSet {
+pub(crate) fn yellow() -> ColorScaleSet {
StaticColorScaleSet {
scale: "Yellow",
light: [
@@ -1110,7 +864,7 @@ fn yellow() -> ColorScaleSet {
.unwrap()
}
-fn amber() -> ColorScaleSet {
+pub(crate) fn amber() -> ColorScaleSet {
StaticColorScaleSet {
scale: "Amber",
light: [
@@ -1174,7 +928,7 @@ fn amber() -> ColorScaleSet {
.unwrap()
}
-fn orange() -> ColorScaleSet {
+pub(crate) fn orange() -> ColorScaleSet {
StaticColorScaleSet {
scale: "Orange",
light: [
@@ -1238,7 +992,7 @@ fn orange() -> ColorScaleSet {
.unwrap()
}
-fn tomato() -> ColorScaleSet {
+pub(crate) fn tomato() -> ColorScaleSet {
StaticColorScaleSet {
scale: "Tomato",
light: [
@@ -1302,7 +1056,7 @@ fn tomato() -> ColorScaleSet {
.unwrap()
}
-fn red() -> ColorScaleSet {
+pub(crate) fn red() -> ColorScaleSet {
StaticColorScaleSet {
scale: "Red",
light: [
@@ -1366,7 +1120,7 @@ fn red() -> ColorScaleSet {
.unwrap()
}
-fn ruby() -> ColorScaleSet {
+pub(crate) fn ruby() -> ColorScaleSet {
StaticColorScaleSet {
scale: "Ruby",
light: [
@@ -1430,7 +1184,7 @@ fn ruby() -> ColorScaleSet {
.unwrap()
}
-fn crimson() -> ColorScaleSet {
+pub(crate) fn crimson() -> ColorScaleSet {
StaticColorScaleSet {
scale: "Crimson",
light: [
@@ -1494,7 +1248,7 @@ fn crimson() -> ColorScaleSet {
.unwrap()
}
-fn pink() -> ColorScaleSet {
+pub(crate) fn pink() -> ColorScaleSet {
StaticColorScaleSet {
scale: "Pink",
light: [
@@ -1558,7 +1312,7 @@ fn pink() -> ColorScaleSet {
.unwrap()
}
-fn plum() -> ColorScaleSet {
+pub(crate) fn plum() -> ColorScaleSet {
StaticColorScaleSet {
scale: "Plum",
light: [
@@ -1622,7 +1376,7 @@ fn plum() -> ColorScaleSet {
.unwrap()
}
-fn purple() -> ColorScaleSet {
+pub(crate) fn purple() -> ColorScaleSet {
StaticColorScaleSet {
scale: "Purple",
light: [
@@ -1686,7 +1440,7 @@ fn purple() -> ColorScaleSet {
.unwrap()
}
-fn violet() -> ColorScaleSet {
+pub(crate) fn violet() -> ColorScaleSet {
StaticColorScaleSet {
scale: "Violet",
light: [
@@ -1750,7 +1504,7 @@ fn violet() -> ColorScaleSet {
.unwrap()
}
-fn iris() -> ColorScaleSet {
+pub(crate) fn iris() -> ColorScaleSet {
StaticColorScaleSet {
scale: "Iris",
light: [
@@ -1814,7 +1568,7 @@ fn iris() -> ColorScaleSet {
.unwrap()
}
-fn indigo() -> ColorScaleSet {
+pub(crate) fn indigo() -> ColorScaleSet {
StaticColorScaleSet {
scale: "Indigo",
light: [
@@ -1878,7 +1632,7 @@ fn indigo() -> ColorScaleSet {
.unwrap()
}
-fn blue() -> ColorScaleSet {
+pub(crate) fn blue() -> ColorScaleSet {
StaticColorScaleSet {
scale: "Blue",
light: [
@@ -1942,7 +1696,7 @@ fn blue() -> ColorScaleSet {
.unwrap()
}
-fn cyan() -> ColorScaleSet {
+pub(crate) fn cyan() -> ColorScaleSet {
StaticColorScaleSet {
scale: "Cyan",
light: [
@@ -2006,7 +1760,7 @@ fn cyan() -> ColorScaleSet {
.unwrap()
}
-fn teal() -> ColorScaleSet {
+pub(crate) fn teal() -> ColorScaleSet {
StaticColorScaleSet {
scale: "Teal",
light: [
@@ -2070,7 +1824,7 @@ fn teal() -> ColorScaleSet {
.unwrap()
}
-fn jade() -> ColorScaleSet {
+pub(crate) fn jade() -> ColorScaleSet {
StaticColorScaleSet {
scale: "Jade",
light: [
@@ -2134,7 +1888,7 @@ fn jade() -> ColorScaleSet {
.unwrap()
}
-fn green() -> ColorScaleSet {
+pub(crate) fn green() -> ColorScaleSet {
StaticColorScaleSet {
scale: "Green",
light: [
@@ -2198,7 +1952,7 @@ fn green() -> ColorScaleSet {
.unwrap()
}
-fn grass() -> ColorScaleSet {
+pub(crate) fn grass() -> ColorScaleSet {
StaticColorScaleSet {
scale: "Grass",
light: [
@@ -2262,7 +2016,7 @@ fn grass() -> ColorScaleSet {
.unwrap()
}
-fn lime() -> ColorScaleSet {
+pub(crate) fn lime() -> ColorScaleSet {
StaticColorScaleSet {
scale: "Lime",
light: [
@@ -2326,7 +2080,7 @@ fn lime() -> ColorScaleSet {
.unwrap()
}
-fn mint() -> ColorScaleSet {
+pub(crate) fn mint() -> ColorScaleSet {
StaticColorScaleSet {
scale: "Mint",
light: [
@@ -2390,7 +2144,7 @@ fn mint() -> ColorScaleSet {
.unwrap()
}
-fn sky() -> ColorScaleSet {
+pub(crate) fn sky() -> ColorScaleSet {
StaticColorScaleSet {
scale: "Sky",
light: [
@@ -2454,7 +2208,7 @@ fn sky() -> ColorScaleSet {
.unwrap()
}
-fn black() -> ColorScaleSet {
+pub(crate) fn black() -> ColorScaleSet {
StaticColorScaleSet {
scale: "Black",
light: [
@@ -2518,7 +2272,7 @@ fn black() -> ColorScaleSet {
.unwrap()
}
-fn white() -> ColorScaleSet {
+pub(crate) fn white() -> ColorScaleSet {
StaticColorScaleSet {
scale: "White",
light: [
@@ -1,58 +1,56 @@
-use std::sync::Arc;
-
use crate::{
- colors::{StatusColors, SystemColors, ThemeColors, ThemeStyles},
- default_color_scales, Appearance, PlayerColors, SyntaxTheme, Theme, ThemeFamily,
+ one_themes::{one_dark, one_family},
+ Theme, ThemeFamily,
};
-fn zed_pro_daylight() -> Theme {
- Theme {
- id: "zed_pro_daylight".to_string(),
- name: "Zed Pro Daylight".into(),
- appearance: Appearance::Light,
- styles: ThemeStyles {
- system: SystemColors::default(),
- colors: ThemeColors::default_light(),
- status: StatusColors::default(),
- player: PlayerColors::default_light(),
- syntax: Arc::new(SyntaxTheme::default_light()),
- },
- }
-}
+// fn zed_pro_daylight() -> Theme {
+// Theme {
+// id: "zed_pro_daylight".to_string(),
+// name: "Zed Pro Daylight".into(),
+// appearance: Appearance::Light,
+// styles: ThemeStyles {
+// system: SystemColors::default(),
+// colors: ThemeColors::light(),
+// status: StatusColors::light(),
+// player: PlayerColors::light(),
+// syntax: Arc::new(SyntaxTheme::light()),
+// },
+// }
+// }
-pub(crate) fn zed_pro_moonlight() -> Theme {
- Theme {
- id: "zed_pro_moonlight".to_string(),
- name: "Zed Pro Moonlight".into(),
- appearance: Appearance::Dark,
- styles: ThemeStyles {
- system: SystemColors::default(),
- colors: ThemeColors::default_dark(),
- status: StatusColors::default(),
- player: PlayerColors::default(),
- syntax: Arc::new(SyntaxTheme::default_dark()),
- },
- }
-}
+// pub(crate) fn zed_pro_moonlight() -> Theme {
+// Theme {
+// id: "zed_pro_moonlight".to_string(),
+// name: "Zed Pro Moonlight".into(),
+// appearance: Appearance::Dark,
+// styles: ThemeStyles {
+// system: SystemColors::default(),
+// colors: ThemeColors::dark(),
+// status: StatusColors::dark(),
+// player: PlayerColors::dark(),
+// syntax: Arc::new(SyntaxTheme::dark()),
+// },
+// }
+// }
-pub fn zed_pro_family() -> ThemeFamily {
- ThemeFamily {
- id: "zed_pro".to_string(),
- name: "Zed Pro".into(),
- author: "Zed Team".into(),
- themes: vec![zed_pro_daylight(), zed_pro_moonlight()],
- scales: default_color_scales(),
- }
-}
+// pub fn zed_pro_family() -> ThemeFamily {
+// ThemeFamily {
+// id: "zed_pro".to_string(),
+// name: "Zed Pro".into(),
+// author: "Zed Team".into(),
+// themes: vec![zed_pro_daylight(), zed_pro_moonlight()],
+// scales: default_color_scales(),
+// }
+// }
impl Default for ThemeFamily {
fn default() -> Self {
- zed_pro_family()
+ one_family()
}
}
impl Default for Theme {
fn default() -> Self {
- zed_pro_daylight()
+ one_dark()
}
}
@@ -0,0 +1,198 @@
+use std::sync::Arc;
+
+use gpui::{hsla, FontStyle, FontWeight, HighlightStyle};
+
+use crate::{
+ default_color_scales, Appearance, PlayerColors, StatusColors, SyntaxTheme, SystemColors, Theme,
+ ThemeColors, ThemeFamily, ThemeStyles,
+};
+
+pub fn one_family() -> ThemeFamily {
+ ThemeFamily {
+ id: "one".to_string(),
+ name: "One".into(),
+ author: "".into(),
+ themes: vec![one_dark()],
+ scales: default_color_scales(),
+ }
+}
+
+pub(crate) fn one_dark() -> Theme {
+ let bg = hsla(215. / 360., 12. / 100., 15. / 100., 1.);
+ let editor = hsla(220. / 360., 12. / 100., 18. / 100., 1.);
+ let elevated_surface = hsla(220. / 360., 12. / 100., 18. / 100., 1.);
+
+ let blue = hsla(207.8 / 360., 81. / 100., 66. / 100., 1.0);
+ let gray = hsla(218.8 / 360., 10. / 100., 40. / 100., 1.0);
+ let green = hsla(95. / 360., 38. / 100., 62. / 100., 1.0);
+ let orange = hsla(29. / 360., 54. / 100., 61. / 100., 1.0);
+ let purple = hsla(286. / 360., 51. / 100., 64. / 100., 1.0);
+ let red = hsla(355. / 360., 65. / 100., 65. / 100., 1.0);
+ let teal = hsla(187. / 360., 47. / 100., 55. / 100., 1.0);
+ let yellow = hsla(39. / 360., 67. / 100., 69. / 100., 1.0);
+
+ Theme {
+ id: "one_dark".to_string(),
+ name: "One Dark".into(),
+ appearance: Appearance::Dark,
+ styles: ThemeStyles {
+ system: SystemColors::default(),
+ colors: ThemeColors {
+ border: hsla(225. / 360., 13. / 100., 12. / 100., 1.),
+ border_variant: hsla(228. / 360., 8. / 100., 25. / 100., 1.),
+ border_focused: hsla(223. / 360., 78. / 100., 65. / 100., 1.),
+ border_selected: hsla(222.6 / 360., 77.5 / 100., 65.1 / 100., 1.0),
+ border_transparent: SystemColors::default().transparent,
+ border_disabled: hsla(222.0 / 360., 11.6 / 100., 33.7 / 100., 1.0),
+ elevated_surface_background: elevated_surface,
+ surface_background: bg,
+ background: bg,
+ element_background: elevated_surface,
+ element_hover: hsla(225.0 / 360., 11.8 / 100., 26.7 / 100., 1.0),
+ element_active: hsla(220.0 / 360., 11.8 / 100., 20.0 / 100., 1.0),
+ element_selected: hsla(224.0 / 360., 11.3 / 100., 26.1 / 100., 1.0),
+ element_disabled: hsla(224.0 / 360., 11.3 / 100., 26.1 / 100., 1.0),
+ drop_target_background: hsla(220.0 / 360., 8.3 / 100., 21.4 / 100., 1.0),
+ ghost_element_background: SystemColors::default().transparent,
+ ghost_element_hover: hsla(225.0 / 360., 11.8 / 100., 26.7 / 100., 1.0),
+ ghost_element_active: hsla(220.0 / 360., 11.8 / 100., 20.0 / 100., 1.0),
+ ghost_element_selected: hsla(224.0 / 360., 11.3 / 100., 26.1 / 100., 1.0),
+ ghost_element_disabled: hsla(224.0 / 360., 11.3 / 100., 26.1 / 100., 1.0),
+ text: hsla(222.9 / 360., 9.1 / 100., 84.9 / 100., 1.0),
+ text_muted: hsla(220.0 / 360., 6.4 / 100., 45.7 / 100., 1.0),
+ text_placeholder: hsla(220.0 / 360., 6.6 / 100., 44.5 / 100., 1.0),
+ text_disabled: hsla(220.0 / 360., 6.6 / 100., 44.5 / 100., 1.0),
+ text_accent: hsla(222.6 / 360., 77.5 / 100., 65.1 / 100., 1.0),
+ icon: hsla(222.9 / 360., 9.9 / 100., 86.1 / 100., 1.0),
+ icon_muted: hsla(220.0 / 360., 12.1 / 100., 66.1 / 100., 1.0),
+ icon_disabled: hsla(220.0 / 360., 6.4 / 100., 45.7 / 100., 1.0),
+ icon_placeholder: hsla(220.0 / 360., 6.4 / 100., 45.7 / 100., 1.0),
+ icon_accent: blue.into(),
+ status_bar_background: bg,
+ title_bar_background: bg,
+ toolbar_background: editor,
+ tab_bar_background: bg,
+ tab_inactive_background: bg,
+ tab_active_background: editor,
+ editor_background: editor,
+ editor_gutter_background: editor,
+ editor_subheader_background: bg,
+ editor_active_line_background: hsla(222.9 / 360., 13.5 / 100., 20.4 / 100., 1.0),
+ editor_highlighted_line_background: hsla(207.8 / 360., 81. / 100., 66. / 100., 0.1),
+ editor_line_number: hsla(222.0 / 360., 11.5 / 100., 34.1 / 100., 1.0),
+ editor_active_line_number: hsla(216.0 / 360., 5.9 / 100., 49.6 / 100., 1.0),
+ editor_invisible: hsla(222.0 / 360., 11.5 / 100., 34.1 / 100., 1.0),
+ editor_wrap_guide: gpui::black(),
+ editor_active_wrap_guide: gpui::red(),
+ editor_document_highlight_read_background: hsla(
+ 207.8 / 360.,
+ 81. / 100.,
+ 66. / 100.,
+ 0.2,
+ ),
+ editor_document_highlight_write_background: gpui::red(),
+ terminal_background: bg,
+ // todo!("Use one colors for terminal")
+ terminal_ansi_black: crate::black().dark().step_12(),
+ terminal_ansi_red: crate::red().dark().step_11(),
+ terminal_ansi_green: crate::green().dark().step_11(),
+ terminal_ansi_yellow: crate::yellow().dark().step_11(),
+ terminal_ansi_blue: crate::blue().dark().step_11(),
+ terminal_ansi_magenta: crate::violet().dark().step_11(),
+ terminal_ansi_cyan: crate::cyan().dark().step_11(),
+ terminal_ansi_white: crate::neutral().dark().step_12(),
+ terminal_ansi_bright_black: crate::black().dark().step_11(),
+ terminal_ansi_bright_red: crate::red().dark().step_10(),
+ terminal_ansi_bright_green: crate::green().dark().step_10(),
+ terminal_ansi_bright_yellow: crate::yellow().dark().step_10(),
+ terminal_ansi_bright_blue: crate::blue().dark().step_10(),
+ terminal_ansi_bright_magenta: crate::violet().dark().step_10(),
+ terminal_ansi_bright_cyan: crate::cyan().dark().step_10(),
+ terminal_ansi_bright_white: crate::neutral().dark().step_11(),
+ },
+ status: StatusColors {
+ conflict: yellow,
+ created: green,
+ deleted: red,
+ error: red,
+ hidden: gray,
+ hint: blue,
+ ignored: gray,
+ info: blue,
+ modified: yellow,
+ predictive: gray,
+ renamed: blue,
+ success: green,
+ unreachable: gray,
+ warning: yellow,
+ },
+ player: PlayerColors::dark(),
+ syntax: Arc::new(SyntaxTheme {
+ highlights: vec![
+ ("attribute".into(), purple.into()),
+ ("boolean".into(), orange.into()),
+ ("comment".into(), gray.into()),
+ ("comment.doc".into(), gray.into()),
+ ("constant".into(), yellow.into()),
+ ("constructor".into(), blue.into()),
+ ("embedded".into(), HighlightStyle::default()),
+ (
+ "emphasis".into(),
+ HighlightStyle {
+ font_style: Some(FontStyle::Italic),
+ ..HighlightStyle::default()
+ },
+ ),
+ (
+ "emphasis.strong".into(),
+ HighlightStyle {
+ font_weight: Some(FontWeight::BOLD),
+ ..HighlightStyle::default()
+ },
+ ),
+ ("enum".into(), HighlightStyle::default()),
+ ("function".into(), blue.into()),
+ ("function.method".into(), blue.into()),
+ ("function.definition".into(), blue.into()),
+ ("hint".into(), blue.into()),
+ ("keyword".into(), purple.into()),
+ ("label".into(), HighlightStyle::default()),
+ ("link_text".into(), blue.into()),
+ (
+ "link_uri".into(),
+ HighlightStyle {
+ color: Some(teal.into()),
+ font_style: Some(FontStyle::Italic),
+ ..HighlightStyle::default()
+ },
+ ),
+ ("number".into(), orange.into()),
+ ("operator".into(), HighlightStyle::default()),
+ ("predictive".into(), HighlightStyle::default()),
+ ("preproc".into(), HighlightStyle::default()),
+ ("primary".into(), HighlightStyle::default()),
+ ("property".into(), red.into()),
+ ("punctuation".into(), HighlightStyle::default()),
+ ("punctuation.bracket".into(), HighlightStyle::default()),
+ ("punctuation.delimiter".into(), HighlightStyle::default()),
+ ("punctuation.list_marker".into(), HighlightStyle::default()),
+ ("punctuation.special".into(), HighlightStyle::default()),
+ ("string".into(), green.into()),
+ ("string.escape".into(), HighlightStyle::default()),
+ ("string.regex".into(), red.into()),
+ ("string.special".into(), HighlightStyle::default()),
+ ("string.special.symbol".into(), HighlightStyle::default()),
+ ("tag".into(), HighlightStyle::default()),
+ ("text.literal".into(), HighlightStyle::default()),
+ ("title".into(), HighlightStyle::default()),
+ ("type".into(), teal.into()),
+ ("variable".into(), HighlightStyle::default()),
+ ("variable.special".into(), red.into()),
+ ("variant".into(), HighlightStyle::default()),
+ ],
+ inlay_style: HighlightStyle::default(),
+ suggestion_style: HighlightStyle::default(),
+ }),
+ },
+ }
+}
@@ -6,8 +6,8 @@ use gpui::{HighlightStyle, SharedString};
use refineable::Refineable;
use crate::{
- zed_pro_family, Appearance, PlayerColors, StatusColors, SyntaxTheme, SystemColors, Theme,
- ThemeColors, ThemeFamily, ThemeStyles, UserTheme, UserThemeFamily,
+ one_themes::one_family, Appearance, PlayerColors, StatusColors, SyntaxTheme, SystemColors,
+ Theme, ThemeColors, ThemeFamily, ThemeStyles, UserTheme, UserThemeFamily,
};
pub struct ThemeRegistry {
@@ -38,17 +38,17 @@ impl ThemeRegistry {
fn insert_user_themes(&mut self, themes: impl IntoIterator<Item = UserTheme>) {
self.insert_themes(themes.into_iter().map(|user_theme| {
let mut theme_colors = match user_theme.appearance {
- Appearance::Light => ThemeColors::default_light(),
- Appearance::Dark => ThemeColors::default_dark(),
+ Appearance::Light => ThemeColors::light(),
+ Appearance::Dark => ThemeColors::dark(),
};
theme_colors.refine(&user_theme.styles.colors);
- let mut status_colors = StatusColors::default();
+ let mut status_colors = StatusColors::dark();
status_colors.refine(&user_theme.styles.status);
let mut syntax_colors = match user_theme.appearance {
- Appearance::Light => SyntaxTheme::default_light(),
- Appearance::Dark => SyntaxTheme::default_dark(),
+ Appearance::Light => SyntaxTheme::light(),
+ Appearance::Dark => SyntaxTheme::dark(),
};
if let Some(user_syntax) = user_theme.styles.syntax {
syntax_colors.highlights = user_syntax
@@ -76,7 +76,10 @@ impl ThemeRegistry {
system: SystemColors::default(),
colors: theme_colors,
status: status_colors,
- player: PlayerColors::default(),
+ player: match user_theme.appearance {
+ Appearance::Light => PlayerColors::light(),
+ Appearance::Dark => PlayerColors::dark(),
+ },
syntax: Arc::new(syntax_colors),
},
}
@@ -105,7 +108,7 @@ impl Default for ThemeRegistry {
themes: HashMap::default(),
};
- this.insert_theme_families([zed_pro_family()]);
+ this.insert_theme_families([one_family()]);
#[cfg(not(feature = "importing-themes"))]
this.insert_user_theme_familes(crate::all_user_themes());
@@ -1,3 +1,4 @@
+use crate::one_themes::one_dark;
use crate::{Theme, ThemeRegistry};
use anyhow::Result;
use gpui::{px, AppContext, Font, FontFeatures, FontStyle, FontWeight, Pixels};
@@ -129,7 +130,7 @@ impl settings::Settings for ThemeSettings {
buffer_line_height: defaults.buffer_line_height.unwrap(),
active_theme: themes
.get(defaults.theme.as_ref().unwrap())
- .or(themes.get("Zed Pro Moonlight"))
+ .or(themes.get(&one_dark().name))
.unwrap(),
};
@@ -0,0 +1,11 @@
+mod colors;
+mod players;
+mod status;
+mod syntax;
+mod system;
+
+pub use colors::*;
+pub use players::*;
+pub use status::*;
+pub use syntax::*;
+pub use system::*;
@@ -1,31 +1,8 @@
-use crate::{PlayerColors, SyntaxTheme};
use gpui::Hsla;
use refineable::Refineable;
use std::sync::Arc;
-#[derive(Clone)]
-pub struct SystemColors {
- pub transparent: Hsla,
- pub mac_os_traffic_light_red: Hsla,
- pub mac_os_traffic_light_yellow: Hsla,
- pub mac_os_traffic_light_green: Hsla,
-}
-
-#[derive(Refineable, Clone, Debug)]
-#[refineable(Debug, serde::Deserialize)]
-pub struct StatusColors {
- pub conflict: Hsla,
- pub created: Hsla,
- pub deleted: Hsla,
- pub error: Hsla,
- pub hidden: Hsla,
- pub ignored: Hsla,
- pub info: Hsla,
- pub modified: Hsla,
- pub renamed: Hsla,
- pub success: Hsla,
- pub warning: Hsla,
-}
+use crate::{PlayerColors, StatusColors, SyntaxTheme, SystemColors};
#[derive(Refineable, Clone, Debug)]
#[refineable(Debug, serde::Deserialize)]
@@ -259,7 +236,7 @@ mod tests {
#[test]
fn override_a_single_theme_color() {
- let mut colors = ThemeColors::default_light();
+ let mut colors = ThemeColors::light();
let magenta: Hsla = gpui::rgb(0xff00ff);
@@ -277,7 +254,7 @@ mod tests {
#[test]
fn override_multiple_theme_colors() {
- let mut colors = ThemeColors::default_light();
+ let mut colors = ThemeColors::light();
let magenta: Hsla = gpui::rgb(0xff00ff);
let green: Hsla = gpui::rgb(0x00ff00);
@@ -16,6 +16,107 @@ pub struct PlayerColor {
#[derive(Clone)]
pub struct PlayerColors(pub Vec<PlayerColor>);
+impl Default for PlayerColors {
+ /// Don't use this!
+ /// We have to have a default to be `[refineable::Refinable]`.
+ /// todo!("Find a way to not need this for Refinable")
+ fn default() -> Self {
+ Self::dark()
+ }
+}
+
+impl PlayerColors {
+ pub fn dark() -> Self {
+ Self(vec![
+ PlayerColor {
+ cursor: blue().dark().step_9(),
+ background: blue().dark().step_5(),
+ selection: blue().dark().step_3(),
+ },
+ PlayerColor {
+ cursor: orange().dark().step_9(),
+ background: orange().dark().step_5(),
+ selection: orange().dark().step_3(),
+ },
+ PlayerColor {
+ cursor: pink().dark().step_9(),
+ background: pink().dark().step_5(),
+ selection: pink().dark().step_3(),
+ },
+ PlayerColor {
+ cursor: lime().dark().step_9(),
+ background: lime().dark().step_5(),
+ selection: lime().dark().step_3(),
+ },
+ PlayerColor {
+ cursor: purple().dark().step_9(),
+ background: purple().dark().step_5(),
+ selection: purple().dark().step_3(),
+ },
+ PlayerColor {
+ cursor: amber().dark().step_9(),
+ background: amber().dark().step_5(),
+ selection: amber().dark().step_3(),
+ },
+ PlayerColor {
+ cursor: jade().dark().step_9(),
+ background: jade().dark().step_5(),
+ selection: jade().dark().step_3(),
+ },
+ PlayerColor {
+ cursor: red().dark().step_9(),
+ background: red().dark().step_5(),
+ selection: red().dark().step_3(),
+ },
+ ])
+ }
+
+ pub fn light() -> Self {
+ Self(vec![
+ PlayerColor {
+ cursor: blue().light().step_9(),
+ background: blue().light().step_4(),
+ selection: blue().light().step_3(),
+ },
+ PlayerColor {
+ cursor: orange().light().step_9(),
+ background: orange().light().step_4(),
+ selection: orange().light().step_3(),
+ },
+ PlayerColor {
+ cursor: pink().light().step_9(),
+ background: pink().light().step_4(),
+ selection: pink().light().step_3(),
+ },
+ PlayerColor {
+ cursor: lime().light().step_9(),
+ background: lime().light().step_4(),
+ selection: lime().light().step_3(),
+ },
+ PlayerColor {
+ cursor: purple().light().step_9(),
+ background: purple().light().step_4(),
+ selection: purple().light().step_3(),
+ },
+ PlayerColor {
+ cursor: amber().light().step_9(),
+ background: amber().light().step_4(),
+ selection: amber().light().step_3(),
+ },
+ PlayerColor {
+ cursor: jade().light().step_9(),
+ background: jade().light().step_4(),
+ selection: jade().light().step_3(),
+ },
+ PlayerColor {
+ cursor: red().light().step_9(),
+ background: red().light().step_4(),
+ selection: red().light().step_3(),
+ },
+ ])
+ }
+}
+
impl PlayerColors {
pub fn local(&self) -> PlayerColor {
// todo!("use a valid color");
@@ -36,6 +137,8 @@ impl PlayerColors {
#[cfg(feature = "stories")]
pub use stories::*;
+use crate::{amber, blue, jade, lime, orange, pink, purple, red};
+
#[cfg(feature = "stories")]
mod stories {
use super::*;
@@ -0,0 +1,134 @@
+use gpui::Hsla;
+use refineable::Refineable;
+
+use crate::{blue, grass, neutral, red, yellow};
+
+#[derive(Refineable, Clone, Debug)]
+#[refineable(Debug, serde::Deserialize)]
+pub struct StatusColors {
+ /// Indicates some kind of conflict, like a file changed on disk while it was open, or
+ /// merge conflicts in a Git repository.
+ pub conflict: Hsla,
+
+ /// Indicates something new, like a new file added to a Git repository.
+ pub created: Hsla,
+
+ /// Indicates that something no longer exists, like a deleted file.
+ pub deleted: Hsla,
+
+ /// Indicates a system error, a failed operation or a diagnostic error.
+ pub error: Hsla,
+
+ /// Represents a hidden status, such as a file being hidden in a file tree.
+ pub hidden: Hsla,
+
+ /// Indicates a hint or some kind of additional information.
+ pub hint: Hsla,
+
+ /// Indicates that something is deliberately ignored, such as a file or operation ignored by Git.
+ pub ignored: Hsla,
+
+ /// Represents informational status updates or messages.
+ pub info: Hsla,
+
+ /// Indicates a changed or altered status, like a file that has been edited.
+ pub modified: Hsla,
+
+ /// Indicates something that is predicted, like automatic code completion, or generated code.
+ pub predictive: Hsla,
+
+ /// Represents a renamed status, such as a file that has been renamed.
+ pub renamed: Hsla,
+
+ /// Indicates a successful operation or task completion.
+ pub success: Hsla,
+
+ /// Indicates some kind of unreachable status, like a block of code that can never be reached.
+ pub unreachable: Hsla,
+
+ /// Represents a warning status, like an operation that is about to fail.
+ pub warning: Hsla,
+}
+
+impl Default for StatusColors {
+ /// Don't use this!
+ /// We have to have a default to be `[refineable::Refinable]`.
+ /// todo!("Find a way to not need this for Refinable")
+ fn default() -> Self {
+ Self::dark()
+ }
+}
+
+pub struct DiagnosticColors {
+ pub error: Hsla,
+ pub warning: Hsla,
+ pub info: Hsla,
+}
+
+pub struct GitStatusColors {
+ pub created: Hsla,
+ pub deleted: Hsla,
+ pub modified: Hsla,
+ pub renamed: Hsla,
+ pub conflict: Hsla,
+ pub ignored: Hsla,
+}
+
+impl StatusColors {
+ pub fn dark() -> Self {
+ Self {
+ conflict: red().dark().step_9(),
+ created: grass().dark().step_9(),
+ deleted: red().dark().step_9(),
+ error: red().dark().step_9(),
+ hidden: neutral().dark().step_9(),
+ hint: blue().dark().step_9(),
+ ignored: neutral().dark().step_9(),
+ info: blue().dark().step_9(),
+ modified: yellow().dark().step_9(),
+ predictive: neutral().dark_alpha().step_9(),
+ renamed: blue().dark().step_9(),
+ success: grass().dark().step_9(),
+ unreachable: neutral().dark().step_10(),
+ warning: yellow().dark().step_9(),
+ }
+ }
+
+ pub fn light() -> Self {
+ Self {
+ conflict: red().light().step_9(),
+ created: grass().light().step_9(),
+ deleted: red().light().step_9(),
+ error: red().light().step_9(),
+ hidden: neutral().light().step_9(),
+ hint: blue().light().step_9(),
+ ignored: neutral().light().step_9(),
+ info: blue().light().step_9(),
+ modified: yellow().light().step_9(),
+ predictive: neutral().light_alpha().step_9(),
+ renamed: blue().light().step_9(),
+ success: grass().light().step_9(),
+ unreachable: neutral().light().step_10(),
+ warning: yellow().light().step_9(),
+ }
+ }
+
+ pub fn diagnostic(&self) -> DiagnosticColors {
+ DiagnosticColors {
+ error: self.error,
+ warning: self.warning,
+ info: self.info,
+ }
+ }
+
+ pub fn git(&self) -> GitStatusColors {
+ GitStatusColors {
+ created: self.created,
+ deleted: self.deleted,
+ modified: self.modified,
+ renamed: self.renamed,
+ conflict: self.conflict,
+ ignored: self.ignored,
+ }
+ }
+}
@@ -0,0 +1,170 @@
+use gpui::{HighlightStyle, Hsla};
+
+use crate::{
+ blue, cyan, gold, indigo, iris, jade, lime, mint, neutral, orange, plum, purple, red, sky,
+ tomato, yellow,
+};
+
+#[derive(Clone, Default)]
+pub struct SyntaxTheme {
+ pub highlights: Vec<(String, HighlightStyle)>,
+ // todo!("Remove this in favor of StatusColor.hint")
+ // If this should be overridable we should move it to ThemeColors
+ pub inlay_style: HighlightStyle,
+ // todo!("Remove this in favor of StatusColor.prediction")
+ // If this should be overridable we should move it to ThemeColors
+ pub suggestion_style: HighlightStyle,
+}
+
+impl SyntaxTheme {
+ pub fn light() -> Self {
+ Self {
+ highlights: vec![
+ ("attribute".into(), cyan().light().step_11().into()),
+ ("boolean".into(), tomato().light().step_11().into()),
+ ("comment".into(), neutral().light().step_11().into()),
+ ("comment.doc".into(), iris().light().step_12().into()),
+ ("constant".into(), red().light().step_9().into()),
+ ("constructor".into(), red().light().step_9().into()),
+ ("embedded".into(), red().light().step_9().into()),
+ ("emphasis".into(), red().light().step_9().into()),
+ ("emphasis.strong".into(), red().light().step_9().into()),
+ ("enum".into(), red().light().step_9().into()),
+ ("function".into(), red().light().step_9().into()),
+ ("hint".into(), red().light().step_9().into()),
+ ("keyword".into(), orange().light().step_11().into()),
+ ("label".into(), red().light().step_9().into()),
+ ("link_text".into(), red().light().step_9().into()),
+ ("link_uri".into(), red().light().step_9().into()),
+ ("number".into(), red().light().step_9().into()),
+ ("operator".into(), red().light().step_9().into()),
+ ("predictive".into(), red().light().step_9().into()),
+ ("preproc".into(), red().light().step_9().into()),
+ ("primary".into(), red().light().step_9().into()),
+ ("property".into(), red().light().step_9().into()),
+ ("punctuation".into(), neutral().light().step_11().into()),
+ (
+ "punctuation.bracket".into(),
+ neutral().light().step_11().into(),
+ ),
+ (
+ "punctuation.delimiter".into(),
+ neutral().light().step_11().into(),
+ ),
+ (
+ "punctuation.list_marker".into(),
+ blue().light().step_11().into(),
+ ),
+ ("punctuation.special".into(), red().light().step_9().into()),
+ ("string".into(), jade().light().step_11().into()),
+ ("string.escape".into(), red().light().step_9().into()),
+ ("string.regex".into(), tomato().light().step_11().into()),
+ ("string.special".into(), red().light().step_9().into()),
+ (
+ "string.special.symbol".into(),
+ red().light().step_9().into(),
+ ),
+ ("tag".into(), red().light().step_9().into()),
+ ("text.literal".into(), red().light().step_9().into()),
+ ("title".into(), red().light().step_9().into()),
+ ("type".into(), red().light().step_9().into()),
+ ("variable".into(), red().light().step_9().into()),
+ ("variable.special".into(), red().light().step_9().into()),
+ ("variant".into(), red().light().step_9().into()),
+ ],
+ inlay_style: tomato().light().step_1().into(), // todo!("nate: use a proper style")
+ suggestion_style: orange().light().step_1().into(), // todo!("nate: use proper style")
+ }
+ }
+
+ pub fn dark() -> Self {
+ Self {
+ highlights: vec![
+ ("attribute".into(), tomato().dark().step_11().into()),
+ ("boolean".into(), tomato().dark().step_11().into()),
+ ("comment".into(), neutral().dark().step_11().into()),
+ ("comment.doc".into(), iris().dark().step_12().into()),
+ ("constant".into(), orange().dark().step_11().into()),
+ ("constructor".into(), gold().dark().step_11().into()),
+ ("embedded".into(), red().dark().step_11().into()),
+ ("emphasis".into(), red().dark().step_11().into()),
+ ("emphasis.strong".into(), red().dark().step_11().into()),
+ ("enum".into(), yellow().dark().step_11().into()),
+ ("function".into(), blue().dark().step_11().into()),
+ ("hint".into(), indigo().dark().step_11().into()),
+ ("keyword".into(), plum().dark().step_11().into()),
+ ("label".into(), red().dark().step_11().into()),
+ ("link_text".into(), red().dark().step_11().into()),
+ ("link_uri".into(), red().dark().step_11().into()),
+ ("number".into(), red().dark().step_11().into()),
+ ("operator".into(), red().dark().step_11().into()),
+ ("predictive".into(), red().dark().step_11().into()),
+ ("preproc".into(), red().dark().step_11().into()),
+ ("primary".into(), red().dark().step_11().into()),
+ ("property".into(), red().dark().step_11().into()),
+ ("punctuation".into(), neutral().dark().step_11().into()),
+ (
+ "punctuation.bracket".into(),
+ neutral().dark().step_11().into(),
+ ),
+ (
+ "punctuation.delimiter".into(),
+ neutral().dark().step_11().into(),
+ ),
+ (
+ "punctuation.list_marker".into(),
+ blue().dark().step_11().into(),
+ ),
+ ("punctuation.special".into(), red().dark().step_11().into()),
+ ("string".into(), lime().dark().step_11().into()),
+ ("string.escape".into(), orange().dark().step_11().into()),
+ ("string.regex".into(), tomato().dark().step_11().into()),
+ ("string.special".into(), red().dark().step_11().into()),
+ (
+ "string.special.symbol".into(),
+ red().dark().step_11().into(),
+ ),
+ ("tag".into(), red().dark().step_11().into()),
+ ("text.literal".into(), purple().dark().step_11().into()),
+ ("title".into(), sky().dark().step_11().into()),
+ ("type".into(), mint().dark().step_11().into()),
+ ("variable".into(), red().dark().step_11().into()),
+ ("variable.special".into(), red().dark().step_11().into()),
+ ("variant".into(), red().dark().step_11().into()),
+ ],
+ inlay_style: neutral().dark().step_11().into(), // todo!("nate: use a proper style")
+ suggestion_style: orange().dark().step_11().into(), // todo!("nate: use a proper style")
+ }
+ }
+
+ // TOOD: Get this working with `#[cfg(test)]`. Why isn't it?
+ pub fn new_test(colors: impl IntoIterator<Item = (&'static str, Hsla)>) -> Self {
+ SyntaxTheme {
+ highlights: colors
+ .into_iter()
+ .map(|(key, color)| {
+ (
+ key.to_owned(),
+ HighlightStyle {
+ color: Some(color),
+ ..Default::default()
+ },
+ )
+ })
+ .collect(),
+ inlay_style: HighlightStyle::default(),
+ suggestion_style: HighlightStyle::default(),
+ }
+ }
+
+ pub fn get(&self, name: &str) -> HighlightStyle {
+ self.highlights
+ .iter()
+ .find_map(|entry| if entry.0 == name { Some(entry.1) } else { None })
+ .unwrap_or_default()
+ }
+
+ pub fn color(&self, name: &str) -> Hsla {
+ self.get(name).color.unwrap_or_default()
+ }
+}
@@ -0,0 +1,20 @@
+use gpui::{hsla, Hsla};
+
+#[derive(Clone)]
+pub struct SystemColors {
+ pub transparent: Hsla,
+ pub mac_os_traffic_light_red: Hsla,
+ pub mac_os_traffic_light_yellow: Hsla,
+ pub mac_os_traffic_light_green: Hsla,
+}
+
+impl Default for SystemColors {
+ fn default() -> Self {
+ Self {
+ transparent: hsla(0.0, 0.0, 0.0, 0.0),
+ mac_os_traffic_light_red: hsla(0.0139, 0.79, 0.65, 1.0),
+ mac_os_traffic_light_yellow: hsla(0.114, 0.88, 0.63, 1.0),
+ mac_os_traffic_light_green: hsla(0.313, 0.49, 0.55, 1.0),
+ }
+ }
+}
@@ -1,41 +0,0 @@
-use gpui::{HighlightStyle, Hsla};
-
-#[derive(Clone, Default)]
-pub struct SyntaxTheme {
- pub highlights: Vec<(String, HighlightStyle)>,
- pub inlay_style: HighlightStyle,
- pub suggestion_style: HighlightStyle,
-}
-
-impl SyntaxTheme {
- // TOOD: Get this working with `#[cfg(test)]`. Why isn't it?
- pub fn new_test(colors: impl IntoIterator<Item = (&'static str, Hsla)>) -> Self {
- SyntaxTheme {
- highlights: colors
- .into_iter()
- .map(|(key, color)| {
- (
- key.to_owned(),
- HighlightStyle {
- color: Some(color),
- ..Default::default()
- },
- )
- })
- .collect(),
- inlay_style: HighlightStyle::default(),
- suggestion_style: HighlightStyle::default(),
- }
- }
-
- pub fn get(&self, name: &str) -> HighlightStyle {
- self.highlights
- .iter()
- .find_map(|entry| if entry.0 == name { Some(entry.1) } else { None })
- .unwrap_or_default()
- }
-
- pub fn color(&self, name: &str) -> Hsla {
- self.get(name).color.unwrap_or_default()
- }
-}
@@ -1,11 +1,10 @@
-mod colors;
mod default_colors;
mod default_theme;
-mod players;
+mod one_themes;
mod registry;
mod scale;
mod settings;
-mod syntax;
+mod styles;
#[cfg(not(feature = "importing-themes"))]
mod themes;
mod user_theme;
@@ -13,14 +12,12 @@ mod user_theme;
use std::sync::Arc;
use ::settings::Settings;
-pub use colors::*;
pub use default_colors::*;
pub use default_theme::*;
-pub use players::*;
pub use registry::*;
pub use scale::*;
pub use settings::*;
-pub use syntax::*;
+pub use styles::*;
#[cfg(not(feature = "importing-themes"))]
pub use themes::*;
pub use user_theme::*;
@@ -0,0 +1,35 @@
+import colorsys
+import sys
+
+def hex_to_rgb(hex):
+ hex = hex.lstrip('#')
+ if len(hex) == 8: # 8 digit hex color
+ r, g, b, a = (int(hex[i:i+2], 16) for i in (0, 2, 4, 6))
+ return r, g, b, a / 255.0
+ else: # 6 digit hex color
+ return tuple(int(hex[i:i+2], 16) for i in (0, 2, 4)) + (1.0,)
+
+def rgb_to_hsla(rgb):
+ h, l, s = colorsys.rgb_to_hls(rgb[0]/255.0, rgb[1]/255.0, rgb[2]/255.0)
+ a = rgb[3] # alpha value
+ return (round(h * 360, 1), round(s * 100, 1), round(l * 100, 1), round(a, 3))
+
+def hex_to_hsla(hex):
+ return rgb_to_hsla(hex_to_rgb(hex))
+
+if len(sys.argv) != 2:
+ print("Usage: python util/hex_to_hsla.py <6 or 8 digit hex color or comma-separated list of colors>")
+else:
+ input_arg = sys.argv[1]
+ if ',' in input_arg: # comma-separated list of colors
+ hex_colors = input_arg.split(',')
+ hslas = [] # output array
+ for hex_color in hex_colors:
+ hex_color = hex_color.strip("'\" ")
+ h, s, l, a = hex_to_hsla(hex_color)
+ hslas.append(f"hsla({h} / 360., {s} / 100., {l} / 100., {a})")
+ print(hslas)
+ else: # single color
+ hex_color = input_arg.strip("'\"")
+ h, s, l, a = hex_to_hsla(hex_color)
+ print(f"hsla({h} / 360., {s} / 100., {l} / 100., {a})")
@@ -1,50 +1,42 @@
-use std::collections::HashSet;
-
-use strum::{EnumIter, IntoEnumIterator};
+use gpui::Action;
+use strum::EnumIter;
use crate::prelude::*;
#[derive(Component)]
-pub struct Keybinding {
+pub struct KeyBinding {
/// A keybinding consists of a key and a set of modifier keys.
/// More then one keybinding produces a chord.
///
/// This should always contain at least one element.
- keybinding: Vec<(String, ModifierKeys)>,
+ key_binding: gpui::KeyBinding,
}
-impl Keybinding {
- pub fn new(key: String, modifiers: ModifierKeys) -> Self {
- Self {
- keybinding: vec![(key, modifiers)],
- }
+impl KeyBinding {
+ pub fn for_action(action: &dyn Action, cx: &mut WindowContext) -> Option<Self> {
+ // todo! this last is arbitrary, we want to prefer users key bindings over defaults,
+ // and vim over normal (in vim mode), etc.
+ let key_binding = cx.bindings_for_action(action).last().cloned()?;
+ Some(Self::new(key_binding))
}
- pub fn new_chord(
- first_note: (String, ModifierKeys),
- second_note: (String, ModifierKeys),
- ) -> Self {
- Self {
- keybinding: vec![first_note, second_note],
- }
+ pub fn new(key_binding: gpui::KeyBinding) -> Self {
+ Self { key_binding }
}
fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> {
div()
.flex()
.gap_2()
- .children(self.keybinding.iter().map(|(key, modifiers)| {
+ .children(self.key_binding.keystrokes().iter().map(|keystroke| {
div()
.flex()
.gap_1()
- .children(ModifierKey::iter().filter_map(|modifier| {
- if modifiers.0.contains(&modifier) {
- Some(Key::new(modifier.glyph().to_string()))
- } else {
- None
- }
- }))
- .child(Key::new(key.clone()))
+ .when(keystroke.modifiers.control, |el| el.child(Key::new("^")))
+ .when(keystroke.modifiers.alt, |el| el.child(Key::new("⌥")))
+ .when(keystroke.modifiers.command, |el| el.child(Key::new("⌘")))
+ .when(keystroke.modifiers.shift, |el| el.child(Key::new("⇧")))
+ .child(Key::new(keystroke.key.clone()))
}))
}
}
@@ -81,76 +73,6 @@ pub enum ModifierKey {
Shift,
}
-impl ModifierKey {
- /// Returns the glyph for the [`ModifierKey`].
- pub fn glyph(&self) -> char {
- match self {
- Self::Control => '^',
- Self::Alt => '⌥',
- Self::Command => '⌘',
- Self::Shift => '⇧',
- }
- }
-}
-
-#[derive(Clone)]
-pub struct ModifierKeys(HashSet<ModifierKey>);
-
-impl ModifierKeys {
- pub fn new() -> Self {
- Self(HashSet::new())
- }
-
- pub fn all() -> Self {
- Self(HashSet::from_iter(ModifierKey::iter()))
- }
-
- pub fn add(mut self, modifier: ModifierKey) -> Self {
- self.0.insert(modifier);
- self
- }
-
- pub fn control(mut self, control: bool) -> Self {
- if control {
- self.0.insert(ModifierKey::Control);
- } else {
- self.0.remove(&ModifierKey::Control);
- }
-
- self
- }
-
- pub fn alt(mut self, alt: bool) -> Self {
- if alt {
- self.0.insert(ModifierKey::Alt);
- } else {
- self.0.remove(&ModifierKey::Alt);
- }
-
- self
- }
-
- pub fn command(mut self, command: bool) -> Self {
- if command {
- self.0.insert(ModifierKey::Command);
- } else {
- self.0.remove(&ModifierKey::Command);
- }
-
- self
- }
-
- pub fn shift(mut self, shift: bool) -> Self {
- if shift {
- self.0.insert(ModifierKey::Shift);
- } else {
- self.0.remove(&ModifierKey::Shift);
- }
-
- self
- }
-}
-
#[cfg(feature = "stories")]
pub use stories::*;
@@ -158,29 +80,38 @@ pub use stories::*;
mod stories {
use super::*;
use crate::Story;
- use gpui::{Div, Render};
+ use gpui::{action, Div, Render};
use itertools::Itertools;
pub struct KeybindingStory;
+ #[action]
+ struct NoAction {}
+
+ pub fn binding(key: &str) -> gpui::KeyBinding {
+ gpui::KeyBinding::new(key, NoAction {}, None)
+ }
+
impl Render for KeybindingStory {
type Element = Div<Self>;
fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
- let all_modifier_permutations = ModifierKey::iter().permutations(2);
+ let all_modifier_permutations =
+ ["ctrl", "alt", "cmd", "shift"].into_iter().permutations(2);
Story::container(cx)
- .child(Story::title_for::<_, Keybinding>(cx))
+ .child(Story::title_for::<_, KeyBinding>(cx))
.child(Story::label(cx, "Single Key"))
- .child(Keybinding::new("Z".to_string(), ModifierKeys::new()))
+ .child(KeyBinding::new(binding("Z")))
.child(Story::label(cx, "Single Key with Modifier"))
.child(
div()
.flex()
.gap_3()
- .children(ModifierKey::iter().map(|modifier| {
- Keybinding::new("C".to_string(), ModifierKeys::new().add(modifier))
- })),
+ .child(KeyBinding::new(binding("ctrl-c")))
+ .child(KeyBinding::new(binding("alt-c")))
+ .child(KeyBinding::new(binding("cmd-c")))
+ .child(KeyBinding::new(binding("shift-c"))),
)
.child(Story::label(cx, "Single Key with Modifier (Permuted)"))
.child(
@@ -194,29 +125,17 @@ mod stories {
.gap_4()
.py_3()
.children(chunk.map(|permutation| {
- let mut modifiers = ModifierKeys::new();
-
- for modifier in permutation {
- modifiers = modifiers.add(modifier);
- }
-
- Keybinding::new("X".to_string(), modifiers)
+ KeyBinding::new(binding(&*(permutation.join("-") + "-x")))
}))
}),
),
)
.child(Story::label(cx, "Single Key with All Modifiers"))
- .child(Keybinding::new("Z".to_string(), ModifierKeys::all()))
+ .child(KeyBinding::new(binding("ctrl-alt-cmd-shift-z")))
.child(Story::label(cx, "Chord"))
- .child(Keybinding::new_chord(
- ("A".to_string(), ModifierKeys::new()),
- ("Z".to_string(), ModifierKeys::new()),
- ))
+ .child(KeyBinding::new(binding("a z")))
.child(Story::label(cx, "Chord with Modifier"))
- .child(Keybinding::new_chord(
- ("A".to_string(), ModifierKeys::new().control(true)),
- ("Z".to_string(), ModifierKeys::new().shift(true)),
- ))
+ .child(KeyBinding::new(binding("ctrl-a shift-z")))
}
}
}
@@ -1,5 +1,5 @@
use crate::prelude::*;
-use crate::{h_stack, v_stack, Keybinding, Label, LabelColor};
+use crate::{h_stack, v_stack, KeyBinding, Label, LabelColor};
#[derive(Component)]
pub struct Palette {
@@ -108,7 +108,7 @@ impl Palette {
pub struct PaletteItem {
pub label: SharedString,
pub sublabel: Option<SharedString>,
- pub keybinding: Option<Keybinding>,
+ pub keybinding: Option<KeyBinding>,
}
impl PaletteItem {
@@ -132,7 +132,7 @@ impl PaletteItem {
pub fn keybinding<K>(mut self, keybinding: K) -> Self
where
- K: Into<Option<Keybinding>>,
+ K: Into<Option<KeyBinding>>,
{
self.keybinding = keybinding.into();
self
@@ -161,7 +161,7 @@ pub use stories::*;
mod stories {
use gpui::{Div, Render};
- use crate::{ModifierKeys, Story};
+ use crate::{binding, Story};
use super::*;
@@ -181,46 +181,24 @@ mod stories {
Palette::new("palette-2")
.placeholder("Execute a command...")
.items(vec![
- PaletteItem::new("theme selector: toggle").keybinding(
- Keybinding::new_chord(
- ("k".to_string(), ModifierKeys::new().command(true)),
- ("t".to_string(), ModifierKeys::new().command(true)),
- ),
- ),
- PaletteItem::new("assistant: inline assist").keybinding(
- Keybinding::new(
- "enter".to_string(),
- ModifierKeys::new().command(true),
- ),
- ),
- PaletteItem::new("assistant: quote selection").keybinding(
- Keybinding::new(
- ">".to_string(),
- ModifierKeys::new().command(true),
- ),
- ),
- PaletteItem::new("assistant: toggle focus").keybinding(
- Keybinding::new(
- "?".to_string(),
- ModifierKeys::new().command(true),
- ),
- ),
+ PaletteItem::new("theme selector: toggle")
+ .keybinding(KeyBinding::new(binding("cmd-k cmd-t"))),
+ PaletteItem::new("assistant: inline assist")
+ .keybinding(KeyBinding::new(binding("cmd-enter"))),
+ PaletteItem::new("assistant: quote selection")
+ .keybinding(KeyBinding::new(binding("cmd-<"))),
+ PaletteItem::new("assistant: toggle focus")
+ .keybinding(KeyBinding::new(binding("cmd-?"))),
PaletteItem::new("auto update: check"),
PaletteItem::new("auto update: view release notes"),
- PaletteItem::new("branches: open recent").keybinding(
- Keybinding::new(
- "b".to_string(),
- ModifierKeys::new().command(true).alt(true),
- ),
- ),
+ PaletteItem::new("branches: open recent")
+ .keybinding(KeyBinding::new(binding("cmd-alt-b"))),
PaletteItem::new("chat panel: toggle focus"),
PaletteItem::new("cli: install"),
PaletteItem::new("client: sign in"),
PaletteItem::new("client: sign out"),
- PaletteItem::new("editor: cancel").keybinding(Keybinding::new(
- "escape".to_string(),
- ModifierKeys::new(),
- )),
+ PaletteItem::new("editor: cancel")
+ .keybinding(KeyBinding::new(binding("escape"))),
]),
)
}
@@ -1,6 +1,8 @@
use gpui::{div, Div, ParentElement, Render, SharedString, Styled, ViewContext};
use theme2::ActiveTheme;
+use crate::StyledExt;
+
#[derive(Clone, Debug)]
pub struct TextTooltip {
title: SharedString,
@@ -16,16 +18,13 @@ impl Render for TextTooltip {
type Element = Div<Self>;
fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
- let theme = cx.theme();
div()
- .bg(theme.colors().background)
- .rounded_lg()
- .border()
+ .elevation_2(cx)
.font("Zed Sans")
- .border_color(theme.colors().border)
- .text_color(theme.colors().text)
- .pl_2()
- .pr_2()
+ .text_ui()
+ .text_color(cx.theme().colors().text)
+ .py_1()
+ .px_2()
.child(self.title.clone())
}
}
@@ -7,12 +7,12 @@ use gpui::{AppContext, ViewContext};
use rand::Rng;
use theme2::ActiveTheme;
-use crate::HighlightedText;
+use crate::{binding, HighlightedText};
use crate::{
Buffer, BufferRow, BufferRows, Button, EditorPane, FileSystemStatus, GitStatus,
- HighlightedLine, Icon, Keybinding, Label, LabelColor, ListEntry, ListEntrySize, Livestream,
- MicStatus, ModifierKeys, Notification, PaletteItem, Player, PlayerCallStatus,
- PlayerWithCallStatus, PublicPlayer, ScreenShareStatus, Symbol, Tab, Toggle, VideoStatus,
+ HighlightedLine, Icon, KeyBinding, Label, LabelColor, ListEntry, ListEntrySize, Livestream,
+ MicStatus, Notification, PaletteItem, Player, PlayerCallStatus, PlayerWithCallStatus,
+ PublicPlayer, ScreenShareStatus, Symbol, Tab, Toggle, VideoStatus,
};
use crate::{ListItem, NotificationAction};
@@ -701,46 +701,16 @@ pub fn static_collab_panel_channels() -> Vec<ListItem> {
pub fn example_editor_actions() -> Vec<PaletteItem> {
vec![
- PaletteItem::new("New File").keybinding(Keybinding::new(
- "N".to_string(),
- ModifierKeys::new().command(true),
- )),
- PaletteItem::new("Open File").keybinding(Keybinding::new(
- "O".to_string(),
- ModifierKeys::new().command(true),
- )),
- PaletteItem::new("Save File").keybinding(Keybinding::new(
- "S".to_string(),
- ModifierKeys::new().command(true),
- )),
- PaletteItem::new("Cut").keybinding(Keybinding::new(
- "X".to_string(),
- ModifierKeys::new().command(true),
- )),
- PaletteItem::new("Copy").keybinding(Keybinding::new(
- "C".to_string(),
- ModifierKeys::new().command(true),
- )),
- PaletteItem::new("Paste").keybinding(Keybinding::new(
- "V".to_string(),
- ModifierKeys::new().command(true),
- )),
- PaletteItem::new("Undo").keybinding(Keybinding::new(
- "Z".to_string(),
- ModifierKeys::new().command(true),
- )),
- PaletteItem::new("Redo").keybinding(Keybinding::new(
- "Z".to_string(),
- ModifierKeys::new().command(true).shift(true),
- )),
- PaletteItem::new("Find").keybinding(Keybinding::new(
- "F".to_string(),
- ModifierKeys::new().command(true),
- )),
- PaletteItem::new("Replace").keybinding(Keybinding::new(
- "R".to_string(),
- ModifierKeys::new().command(true),
- )),
+ PaletteItem::new("New File").keybinding(KeyBinding::new(binding("cmd-n"))),
+ PaletteItem::new("Open File").keybinding(KeyBinding::new(binding("cmd-o"))),
+ PaletteItem::new("Save File").keybinding(KeyBinding::new(binding("cmd-s"))),
+ PaletteItem::new("Cut").keybinding(KeyBinding::new(binding("cmd-x"))),
+ PaletteItem::new("Copy").keybinding(KeyBinding::new(binding("cmd-c"))),
+ PaletteItem::new("Paste").keybinding(KeyBinding::new(binding("cmd-v"))),
+ PaletteItem::new("Undo").keybinding(KeyBinding::new(binding("cmd-z"))),
+ PaletteItem::new("Redo").keybinding(KeyBinding::new(binding("cmd-shift-z"))),
+ PaletteItem::new("Find").keybinding(KeyBinding::new(binding("cmd-f"))),
+ PaletteItem::new("Replace").keybinding(KeyBinding::new(binding("cmd-r"))),
PaletteItem::new("Jump to Line"),
PaletteItem::new("Select All"),
PaletteItem::new("Deselect All"),
@@ -1,4 +1,4 @@
-use gpui::{Div, ElementFocus, ElementInteractivity, Styled, UniformList, ViewContext};
+use gpui::{Div, ElementInteractivity, KeyDispatch, Styled, UniformList, ViewContext};
use theme2::ActiveTheme;
use crate::{ElevationIndex, UITextSize};
@@ -96,7 +96,7 @@ pub trait StyledExt: Styled + Sized {
impl<V, I, F> StyledExt for Div<V, I, F>
where
I: ElementInteractivity<V>,
- F: ElementFocus<V>,
+ F: KeyDispatch<V>,
{
}
@@ -1401,20 +1401,32 @@ impl Pane {
// .on_drop(|_view, state: View<DraggedTab>, cx| {
// eprintln!("{:?}", state.read(cx));
// })
- .px_2()
- .py_0p5()
.flex()
.items_center()
.justify_center()
+ // todo!("Nate - I need to do some work to balance all the items in the tab once things stablize")
+ .map(|this| {
+ if close_right {
+ this.pl_3().pr_1()
+ } else {
+ this.pr_1().pr_3()
+ }
+ })
+ .py_1()
.bg(tab_bg)
- .hover(|h| h.bg(tab_hover_bg))
- .active(|a| a.bg(tab_active_bg))
+ .border_color(cx.theme().colors().border)
+ .map(|this| match ix.cmp(&self.active_item_index) {
+ cmp::Ordering::Less => this.border_l(),
+ cmp::Ordering::Equal => this.border_r(),
+ cmp::Ordering::Greater => this.border_l().border_r(),
+ })
+ // .hover(|h| h.bg(tab_hover_bg))
+ // .active(|a| a.bg(tab_active_bg))
.child(
div()
- .px_1()
.flex()
.items_center()
- .gap_1p5()
+ .gap_1()
.text_color(text_color)
.children(if item.has_conflict(cx) {
Some(
@@ -37,10 +37,10 @@ use futures::{
};
use gpui::{
actions, div, point, rems, size, Action, AnyModel, AnyView, AnyWeakView, AppContext,
- AsyncAppContext, AsyncWindowContext, Bounds, Component, DispatchContext, Div, Entity, EntityId,
- EventEmitter, FocusHandle, GlobalPixels, Model, ModelContext, ParentElement, Point, Render,
- Size, StatefulInteractive, StatefulInteractivity, StatelessInteractive, Styled, Subscription,
- Task, View, ViewContext, VisualContext, WeakView, WindowBounds, WindowContext, WindowHandle,
+ AsyncAppContext, AsyncWindowContext, Bounds, Component, Div, Entity, EntityId, EventEmitter,
+ FocusHandle, GlobalPixels, KeyContext, Model, ModelContext, ParentElement, Point, Render, Size,
+ StatefulInteractive, StatelessInteractive, StatelessInteractivity, Styled, Subscription, Task,
+ View, ViewContext, VisualContext, WeakView, WindowBounds, WindowContext, WindowHandle,
WindowOptions,
};
use item::{FollowableItem, FollowableItemHandle, Item, ItemHandle, ItemSettings, ProjectItem};
@@ -534,8 +534,8 @@ pub struct Workspace {
workspace_actions: Vec<
Box<
dyn Fn(
- Div<Workspace, StatefulInteractivity<Workspace>>,
- ) -> Div<Workspace, StatefulInteractivity<Workspace>>,
+ Div<Workspace, StatelessInteractivity<Workspace>>,
+ ) -> Div<Workspace, StatelessInteractivity<Workspace>>,
>,
>,
zoomed: Option<AnyWeakView>,
@@ -3517,8 +3517,8 @@ impl Workspace {
fn add_workspace_actions_listeners(
&self,
- mut div: Div<Workspace, StatefulInteractivity<Workspace>>,
- ) -> Div<Workspace, StatefulInteractivity<Workspace>> {
+ mut div: Div<Workspace, StatelessInteractivity<Workspace>>,
+ ) -> Div<Workspace, StatelessInteractivity<Workspace>> {
for action in self.workspace_actions.iter() {
div = (action)(div)
}
@@ -3746,158 +3746,159 @@ impl Render for Workspace {
type Element = Div<Self>;
fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
- let mut context = DispatchContext::default();
- context.insert("Workspace");
- cx.with_key_dispatch_context(context, |cx| {
- div()
- .relative()
- .size_full()
- .flex()
- .flex_col()
- .font("Zed Sans")
- .gap_0()
- .justify_start()
- .items_start()
- .text_color(cx.theme().colors().text)
- .bg(cx.theme().colors().background)
- .child(self.render_titlebar(cx))
- .child(
- // todo! should this be a component a view?
- self.add_workspace_actions_listeners(div().id("workspace"))
- .relative()
- .flex_1()
- .w_full()
- .flex()
- .overflow_hidden()
- .border_t()
- .border_b()
- .border_color(cx.theme().colors().border)
- .child(self.modal_layer.clone())
- // .children(
- // Some(
- // Panel::new("project-panel-outer", cx)
- // .side(PanelSide::Left)
- // .child(ProjectPanel::new("project-panel-inner")),
- // )
- // .filter(|_| self.is_project_panel_open()),
- // )
- // .children(
- // Some(
- // Panel::new("collab-panel-outer", cx)
- // .child(CollabPanel::new("collab-panel-inner"))
- // .side(PanelSide::Left),
- // )
- // .filter(|_| self.is_collab_panel_open()),
- // )
- // .child(NotificationToast::new(
- // "maxbrunsfeld has requested to add you as a contact.".into(),
- // ))
- .child(
- div().flex().flex_col().flex_1().h_full().child(
- div().flex().flex_1().child(self.center.render(
- &self.project,
- &self.follower_states,
- self.active_call(),
- &self.active_pane,
- self.zoomed.as_ref(),
- &self.app_state,
- cx,
- )),
- ), // .children(
- // Some(
- // Panel::new("terminal-panel", cx)
- // .child(Terminal::new())
- // .allowed_sides(PanelAllowedSides::BottomOnly)
- // .side(PanelSide::Bottom),
- // )
- // .filter(|_| self.is_terminal_open()),
- // ),
+ let mut context = KeyContext::default();
+ context.add("Workspace");
+
+ self.add_workspace_actions_listeners(div())
+ .context(context)
+ .relative()
+ .size_full()
+ .flex()
+ .flex_col()
+ .font("Zed Sans")
+ .gap_0()
+ .justify_start()
+ .items_start()
+ .text_color(cx.theme().colors().text)
+ .bg(cx.theme().colors().background)
+ .child(self.render_titlebar(cx))
+ .child(
+ // todo! should this be a component a view?
+ div()
+ .id("workspace")
+ .relative()
+ .flex_1()
+ .w_full()
+ .flex()
+ .overflow_hidden()
+ .border_t()
+ .border_b()
+ .border_color(cx.theme().colors().border)
+ .child(self.modal_layer.clone())
+ // .children(
+ // Some(
+ // Panel::new("project-panel-outer", cx)
+ // .side(PanelSide::Left)
+ // .child(ProjectPanel::new("project-panel-inner")),
+ // )
+ // .filter(|_| self.is_project_panel_open()),
+ // )
+ // .children(
+ // Some(
+ // Panel::new("collab-panel-outer", cx)
+ // .child(CollabPanel::new("collab-panel-inner"))
+ // .side(PanelSide::Left),
+ // )
+ // .filter(|_| self.is_collab_panel_open()),
+ // )
+ // .child(NotificationToast::new(
+ // "maxbrunsfeld has requested to add you as a contact.".into(),
+ // ))
+ .child(
+ div().flex().flex_col().flex_1().h_full().child(
+ div().flex().flex_1().child(self.center.render(
+ &self.project,
+ &self.follower_states,
+ self.active_call(),
+ &self.active_pane,
+ self.zoomed.as_ref(),
+ &self.app_state,
+ cx,
+ )),
), // .children(
// Some(
- // Panel::new("chat-panel-outer", cx)
- // .side(PanelSide::Right)
- // .child(ChatPanel::new("chat-panel-inner").messages(vec![
- // ChatMessage::new(
- // "osiewicz".to_string(),
- // "is this thing on?".to_string(),
- // DateTime::parse_from_rfc3339("2023-09-27T15:40:52.707Z")
- // .unwrap()
- // .naive_local(),
- // ),
- // ChatMessage::new(
- // "maxdeviant".to_string(),
- // "Reading you loud and clear!".to_string(),
- // DateTime::parse_from_rfc3339("2023-09-28T15:40:52.707Z")
- // .unwrap()
- // .naive_local(),
- // ),
- // ])),
+ // Panel::new("terminal-panel", cx)
+ // .child(Terminal::new())
+ // .allowed_sides(PanelAllowedSides::BottomOnly)
+ // .side(PanelSide::Bottom),
// )
- // .filter(|_| self.is_chat_panel_open()),
- // )
- // .children(
- // Some(
- // Panel::new("notifications-panel-outer", cx)
- // .side(PanelSide::Right)
- // .child(NotificationsPanel::new("notifications-panel-inner")),
- // )
- // .filter(|_| self.is_notifications_panel_open()),
- // )
- // .children(
- // Some(
- // Panel::new("assistant-panel-outer", cx)
- // .child(AssistantPanel::new("assistant-panel-inner")),
- // )
- // .filter(|_| self.is_assistant_panel_open()),
+ // .filter(|_| self.is_terminal_open()),
// ),
- )
- .child(self.status_bar.clone())
- // .when(self.debug.show_toast, |this| {
- // this.child(Toast::new(ToastOrigin::Bottom).child(Label::new("A toast")))
- // })
- // .children(
- // Some(
- // div()
- // .absolute()
- // .top(px(50.))
- // .left(px(640.))
- // .z_index(8)
- // .child(LanguageSelector::new("language-selector")),
- // )
- // .filter(|_| self.is_language_selector_open()),
- // )
- .z_index(8)
- // Debug
- .child(
- div()
- .flex()
- .flex_col()
- .z_index(9)
- .absolute()
- .top_20()
- .left_1_4()
- .w_40()
- .gap_2(), // .when(self.show_debug, |this| {
- // this.child(Button::<Workspace>::new("Toggle User Settings").on_click(
- // Arc::new(|workspace, cx| workspace.debug_toggle_user_settings(cx)),
- // ))
- // .child(
- // Button::<Workspace>::new("Toggle Toasts").on_click(Arc::new(
- // |workspace, cx| workspace.debug_toggle_toast(cx),
- // )),
- // )
- // .child(
- // Button::<Workspace>::new("Toggle Livestream").on_click(Arc::new(
- // |workspace, cx| workspace.debug_toggle_livestream(cx),
- // )),
- // )
- // })
- // .child(
- // Button::<Workspace>::new("Toggle Debug")
- // .on_click(Arc::new(|workspace, cx| workspace.toggle_debug(cx))),
- // ),
- )
- })
+ ), // .children(
+ // Some(
+ // Panel::new("chat-panel-outer", cx)
+ // .side(PanelSide::Right)
+ // .child(ChatPanel::new("chat-panel-inner").messages(vec![
+ // ChatMessage::new(
+ // "osiewicz".to_string(),
+ // "is this thing on?".to_string(),
+ // DateTime::parse_from_rfc3339("2023-09-27T15:40:52.707Z")
+ // .unwrap()
+ // .naive_local(),
+ // ),
+ // ChatMessage::new(
+ // "maxdeviant".to_string(),
+ // "Reading you loud and clear!".to_string(),
+ // DateTime::parse_from_rfc3339("2023-09-28T15:40:52.707Z")
+ // .unwrap()
+ // .naive_local(),
+ // ),
+ // ])),
+ // )
+ // .filter(|_| self.is_chat_panel_open()),
+ // )
+ // .children(
+ // Some(
+ // Panel::new("notifications-panel-outer", cx)
+ // .side(PanelSide::Right)
+ // .child(NotificationsPanel::new("notifications-panel-inner")),
+ // )
+ // .filter(|_| self.is_notifications_panel_open()),
+ // )
+ // .children(
+ // Some(
+ // Panel::new("assistant-panel-outer", cx)
+ // .child(AssistantPanel::new("assistant-panel-inner")),
+ // )
+ // .filter(|_| self.is_assistant_panel_open()),
+ // ),
+ )
+ .child(self.status_bar.clone())
+ // .when(self.debug.show_toast, |this| {
+ // this.child(Toast::new(ToastOrigin::Bottom).child(Label::new("A toast")))
+ // })
+ // .children(
+ // Some(
+ // div()
+ // .absolute()
+ // .top(px(50.))
+ // .left(px(640.))
+ // .z_index(8)
+ // .child(LanguageSelector::new("language-selector")),
+ // )
+ // .filter(|_| self.is_language_selector_open()),
+ // )
+ .z_index(8)
+ // Debug
+ .child(
+ div()
+ .flex()
+ .flex_col()
+ .z_index(9)
+ .absolute()
+ .top_20()
+ .left_1_4()
+ .w_40()
+ .gap_2(), // .when(self.show_debug, |this| {
+ // this.child(Button::<Workspace>::new("Toggle User Settings").on_click(
+ // Arc::new(|workspace, cx| workspace.debug_toggle_user_settings(cx)),
+ // ))
+ // .child(
+ // Button::<Workspace>::new("Toggle Toasts").on_click(Arc::new(
+ // |workspace, cx| workspace.debug_toggle_toast(cx),
+ // )),
+ // )
+ // .child(
+ // Button::<Workspace>::new("Toggle Livestream").on_click(Arc::new(
+ // |workspace, cx| workspace.debug_toggle_livestream(cx),
+ // )),
+ // )
+ // })
+ // .child(
+ // Button::<Workspace>::new("Toggle Debug")
+ // .on_click(Arc::new(|workspace, cx| workspace.toggle_debug(cx))),
+ // ),
+ )
}
}
// todo!()