diff --git a/crates/gpui/Cargo.toml b/crates/gpui/Cargo.toml index f0f9ab847b..ef5f5b1c4b 100644 --- a/crates/gpui/Cargo.toml +++ b/crates/gpui/Cargo.toml @@ -256,10 +256,6 @@ path = "examples/legacy/hello_world.rs" name = "image_loading" path = "examples/legacy/image_loading.rs" -[[example]] -name = "input" -path = "examples/legacy/input.rs" - [[example]] name = "layer_shell" path = "examples/legacy/layer_shell.rs" diff --git a/crates/gpui/examples/legacy/input.rs b/crates/gpui/examples/legacy/input.rs deleted file mode 100644 index d9b758add3..0000000000 --- a/crates/gpui/examples/legacy/input.rs +++ /dev/null @@ -1,753 +0,0 @@ -use std::ops::Range; - -use gpui::{ - App, Bounds, ClipboardItem, Context, CursorStyle, ElementId, ElementInputHandler, Entity, - EntityInputHandler, FocusHandle, Focusable, GlobalElementId, KeyBinding, Keystroke, LayoutId, - MouseButton, MouseDownEvent, MouseMoveEvent, MouseUpEvent, PaintQuad, Pixels, Point, - ShapedLine, SharedString, Style, TextRun, UTF16Selection, UnderlineStyle, Window, WindowBounds, - WindowOptions, actions, black, div, fill, hsla, opaque_grey, point, prelude::*, px, relative, - rgb, rgba, size, white, yellow, -}; -use unicode_segmentation::*; - -actions!( - text_input, - [ - Backspace, - Delete, - Left, - Right, - SelectLeft, - SelectRight, - SelectAll, - Home, - End, - ShowCharacterPalette, - Paste, - Cut, - Copy, - Quit, - ] -); - -struct TextInput { - focus_handle: FocusHandle, - content: SharedString, - placeholder: SharedString, - selected_range: Range, - selection_reversed: bool, - marked_range: Option>, - last_layout: Option, - last_bounds: Option>, - is_selecting: bool, -} - -impl TextInput { - fn left(&mut self, _: &Left, _: &mut Window, cx: &mut Context) { - if self.selected_range.is_empty() { - self.move_to(self.previous_boundary(self.cursor_offset()), cx); - } else { - self.move_to(self.selected_range.start, cx) - } - } - - fn right(&mut self, _: &Right, _: &mut Window, cx: &mut Context) { - if self.selected_range.is_empty() { - self.move_to(self.next_boundary(self.selected_range.end), cx); - } else { - self.move_to(self.selected_range.end, cx) - } - } - - fn select_left(&mut self, _: &SelectLeft, _: &mut Window, cx: &mut Context) { - self.select_to(self.previous_boundary(self.cursor_offset()), cx); - } - - fn select_right(&mut self, _: &SelectRight, _: &mut Window, cx: &mut Context) { - self.select_to(self.next_boundary(self.cursor_offset()), cx); - } - - fn select_all(&mut self, _: &SelectAll, _: &mut Window, cx: &mut Context) { - self.move_to(0, cx); - self.select_to(self.content.len(), cx) - } - - fn home(&mut self, _: &Home, _: &mut Window, cx: &mut Context) { - self.move_to(0, cx); - } - - fn end(&mut self, _: &End, _: &mut Window, cx: &mut Context) { - self.move_to(self.content.len(), cx); - } - - fn backspace(&mut self, _: &Backspace, window: &mut Window, cx: &mut Context) { - if self.selected_range.is_empty() { - self.select_to(self.previous_boundary(self.cursor_offset()), cx) - } - self.replace_text_in_range(None, "", window, cx) - } - - fn delete(&mut self, _: &Delete, window: &mut Window, cx: &mut Context) { - if self.selected_range.is_empty() { - self.select_to(self.next_boundary(self.cursor_offset()), cx) - } - self.replace_text_in_range(None, "", window, cx) - } - - fn on_mouse_down( - &mut self, - event: &MouseDownEvent, - _window: &mut Window, - cx: &mut Context, - ) { - self.is_selecting = true; - - if event.modifiers.shift { - self.select_to(self.index_for_mouse_position(event.position), cx); - } else { - self.move_to(self.index_for_mouse_position(event.position), cx) - } - } - - fn on_mouse_up(&mut self, _: &MouseUpEvent, _window: &mut Window, _: &mut Context) { - self.is_selecting = false; - } - - fn on_mouse_move(&mut self, event: &MouseMoveEvent, _: &mut Window, cx: &mut Context) { - if self.is_selecting { - self.select_to(self.index_for_mouse_position(event.position), cx); - } - } - - fn show_character_palette( - &mut self, - _: &ShowCharacterPalette, - window: &mut Window, - _: &mut Context, - ) { - window.show_character_palette(); - } - - fn paste(&mut self, _: &Paste, window: &mut Window, cx: &mut Context) { - if let Some(text) = cx.read_from_clipboard().and_then(|item| item.text()) { - self.replace_text_in_range(None, &text.replace("\n", " "), window, cx); - } - } - - fn copy(&mut self, _: &Copy, _: &mut Window, cx: &mut Context) { - if !self.selected_range.is_empty() { - cx.write_to_clipboard(ClipboardItem::new_string( - self.content[self.selected_range.clone()].to_string(), - )); - } - } - fn cut(&mut self, _: &Cut, window: &mut Window, cx: &mut Context) { - if !self.selected_range.is_empty() { - cx.write_to_clipboard(ClipboardItem::new_string( - self.content[self.selected_range.clone()].to_string(), - )); - self.replace_text_in_range(None, "", window, cx) - } - } - - fn move_to(&mut self, offset: usize, cx: &mut Context) { - self.selected_range = offset..offset; - cx.notify() - } - - fn cursor_offset(&self) -> usize { - if self.selection_reversed { - self.selected_range.start - } else { - self.selected_range.end - } - } - - fn index_for_mouse_position(&self, position: Point) -> usize { - if self.content.is_empty() { - return 0; - } - - let (Some(bounds), Some(line)) = (self.last_bounds.as_ref(), self.last_layout.as_ref()) - else { - return 0; - }; - if position.y < bounds.top() { - return 0; - } - if position.y > bounds.bottom() { - return self.content.len(); - } - line.closest_index_for_x(position.x - bounds.left()) - } - - fn select_to(&mut self, offset: usize, cx: &mut Context) { - if self.selection_reversed { - self.selected_range.start = offset - } else { - self.selected_range.end = offset - }; - if self.selected_range.end < self.selected_range.start { - self.selection_reversed = !self.selection_reversed; - self.selected_range = self.selected_range.end..self.selected_range.start; - } - cx.notify() - } - - fn offset_from_utf16(&self, offset: usize) -> usize { - let mut utf8_offset = 0; - let mut utf16_count = 0; - - for ch in self.content.chars() { - if utf16_count >= offset { - break; - } - utf16_count += ch.len_utf16(); - utf8_offset += ch.len_utf8(); - } - - utf8_offset - } - - fn offset_to_utf16(&self, offset: usize) -> usize { - let mut utf16_offset = 0; - let mut utf8_count = 0; - - for ch in self.content.chars() { - if utf8_count >= offset { - break; - } - utf8_count += ch.len_utf8(); - utf16_offset += ch.len_utf16(); - } - - utf16_offset - } - - fn range_to_utf16(&self, range: &Range) -> Range { - self.offset_to_utf16(range.start)..self.offset_to_utf16(range.end) - } - - fn range_from_utf16(&self, range_utf16: &Range) -> Range { - self.offset_from_utf16(range_utf16.start)..self.offset_from_utf16(range_utf16.end) - } - - fn previous_boundary(&self, offset: usize) -> usize { - self.content - .grapheme_indices(true) - .rev() - .find_map(|(idx, _)| (idx < offset).then_some(idx)) - .unwrap_or(0) - } - - fn next_boundary(&self, offset: usize) -> usize { - self.content - .grapheme_indices(true) - .find_map(|(idx, _)| (idx > offset).then_some(idx)) - .unwrap_or(self.content.len()) - } - - fn reset(&mut self) { - self.content = "".into(); - self.selected_range = 0..0; - self.selection_reversed = false; - self.marked_range = None; - self.last_layout = None; - self.last_bounds = None; - self.is_selecting = false; - } -} - -impl EntityInputHandler for TextInput { - fn text_for_range( - &mut self, - range_utf16: Range, - actual_range: &mut Option>, - _window: &mut Window, - _cx: &mut Context, - ) -> Option { - let range = self.range_from_utf16(&range_utf16); - actual_range.replace(self.range_to_utf16(&range)); - Some(self.content[range].to_string()) - } - - fn selected_text_range( - &mut self, - _ignore_disabled_input: bool, - _window: &mut Window, - _cx: &mut Context, - ) -> Option { - Some(UTF16Selection { - range: self.range_to_utf16(&self.selected_range), - reversed: self.selection_reversed, - }) - } - - fn marked_text_range( - &self, - _window: &mut Window, - _cx: &mut Context, - ) -> Option> { - self.marked_range - .as_ref() - .map(|range| self.range_to_utf16(range)) - } - - fn unmark_text(&mut self, _window: &mut Window, _cx: &mut Context) { - self.marked_range = None; - } - - fn replace_text_in_range( - &mut self, - range_utf16: Option>, - new_text: &str, - _: &mut Window, - cx: &mut Context, - ) { - let range = range_utf16 - .as_ref() - .map(|range_utf16| self.range_from_utf16(range_utf16)) - .or(self.marked_range.clone()) - .unwrap_or(self.selected_range.clone()); - - self.content = - (self.content[0..range.start].to_owned() + new_text + &self.content[range.end..]) - .into(); - self.selected_range = range.start + new_text.len()..range.start + new_text.len(); - self.marked_range.take(); - cx.notify(); - } - - fn replace_and_mark_text_in_range( - &mut self, - range_utf16: Option>, - new_text: &str, - new_selected_range_utf16: Option>, - _window: &mut Window, - cx: &mut Context, - ) { - let range = range_utf16 - .as_ref() - .map(|range_utf16| self.range_from_utf16(range_utf16)) - .or(self.marked_range.clone()) - .unwrap_or(self.selected_range.clone()); - - self.content = - (self.content[0..range.start].to_owned() + new_text + &self.content[range.end..]) - .into(); - if !new_text.is_empty() { - self.marked_range = Some(range.start..range.start + new_text.len()); - } else { - self.marked_range = None; - } - self.selected_range = new_selected_range_utf16 - .as_ref() - .map(|range_utf16| self.range_from_utf16(range_utf16)) - .map(|new_range| new_range.start + range.start..new_range.end + range.end) - .unwrap_or_else(|| range.start + new_text.len()..range.start + new_text.len()); - - cx.notify(); - } - - fn bounds_for_range( - &mut self, - range_utf16: Range, - bounds: Bounds, - _window: &mut Window, - _cx: &mut Context, - ) -> Option> { - let last_layout = self.last_layout.as_ref()?; - let range = self.range_from_utf16(&range_utf16); - Some(Bounds::from_corners( - point( - bounds.left() + last_layout.x_for_index(range.start), - bounds.top(), - ), - point( - bounds.left() + last_layout.x_for_index(range.end), - bounds.bottom(), - ), - )) - } - - fn character_index_for_point( - &mut self, - point: gpui::Point, - _window: &mut Window, - _cx: &mut Context, - ) -> Option { - let line_point = self.last_bounds?.localize(&point)?; - let last_layout = self.last_layout.as_ref()?; - - assert_eq!(last_layout.text, self.content); - let utf8_index = last_layout.index_for_x(point.x - line_point.x)?; - Some(self.offset_to_utf16(utf8_index)) - } -} - -struct TextElement { - input: Entity, -} - -struct PrepaintState { - line: Option, - cursor: Option, - selection: Option, -} - -impl IntoElement for TextElement { - type Element = Self; - - fn into_element(self) -> Self::Element { - self - } -} - -impl Element for TextElement { - type RequestLayoutState = (); - type PrepaintState = PrepaintState; - - fn id(&self) -> Option { - None - } - - fn source_location(&self) -> Option<&'static core::panic::Location<'static>> { - None - } - - fn request_layout( - &mut self, - _id: Option<&GlobalElementId>, - _inspector_id: Option<&gpui::InspectorElementId>, - window: &mut Window, - cx: &mut App, - ) -> (LayoutId, Self::RequestLayoutState) { - let mut style = Style::default(); - style.size.width = relative(1.).into(); - style.size.height = window.line_height().into(); - (window.request_layout(style, [], cx), ()) - } - - fn prepaint( - &mut self, - _id: Option<&GlobalElementId>, - _inspector_id: Option<&gpui::InspectorElementId>, - bounds: Bounds, - _request_layout: &mut Self::RequestLayoutState, - window: &mut Window, - cx: &mut App, - ) -> Self::PrepaintState { - let input = self.input.read(cx); - let content = input.content.clone(); - let selected_range = input.selected_range.clone(); - let cursor = input.cursor_offset(); - let style = window.text_style(); - - let (display_text, text_color) = if content.is_empty() { - (input.placeholder.clone(), hsla(0., 0., 0., 0.2)) - } else { - (content, style.color) - }; - - let run = TextRun { - len: display_text.len(), - font: style.font(), - color: text_color, - background_color: None, - underline: None, - strikethrough: None, - }; - let runs = if let Some(marked_range) = input.marked_range.as_ref() { - vec![ - TextRun { - len: marked_range.start, - ..run.clone() - }, - TextRun { - len: marked_range.end - marked_range.start, - underline: Some(UnderlineStyle { - color: Some(run.color), - thickness: px(1.0), - wavy: false, - }), - ..run.clone() - }, - TextRun { - len: display_text.len() - marked_range.end, - ..run - }, - ] - .into_iter() - .filter(|run| run.len > 0) - .collect() - } else { - vec![run] - }; - - let font_size = style.font_size.to_pixels(window.rem_size()); - let line = window - .text_system() - .shape_line(display_text, font_size, &runs, None); - - let cursor_pos = line.x_for_index(cursor); - let (selection, cursor) = if selected_range.is_empty() { - ( - None, - Some(fill( - Bounds::new( - point(bounds.left() + cursor_pos, bounds.top()), - size(px(2.), bounds.bottom() - bounds.top()), - ), - gpui::blue(), - )), - ) - } else { - ( - Some(fill( - Bounds::from_corners( - point( - bounds.left() + line.x_for_index(selected_range.start), - bounds.top(), - ), - point( - bounds.left() + line.x_for_index(selected_range.end), - bounds.bottom(), - ), - ), - rgba(0x3311ff30), - )), - None, - ) - }; - PrepaintState { - line: Some(line), - cursor, - selection, - } - } - - fn paint( - &mut self, - _id: Option<&GlobalElementId>, - _inspector_id: Option<&gpui::InspectorElementId>, - bounds: Bounds, - _request_layout: &mut Self::RequestLayoutState, - prepaint: &mut Self::PrepaintState, - window: &mut Window, - cx: &mut App, - ) { - let focus_handle = self.input.read(cx).focus_handle.clone(); - window.handle_input( - &focus_handle, - ElementInputHandler::new(bounds, self.input.clone()), - cx, - ); - if let Some(selection) = prepaint.selection.take() { - window.paint_quad(selection) - } - let line = prepaint.line.take().unwrap(); - line.paint( - bounds.origin, - window.line_height(), - gpui::TextAlign::Left, - None, - window, - cx, - ) - .unwrap(); - - if focus_handle.is_focused(window) - && let Some(cursor) = prepaint.cursor.take() - { - window.paint_quad(cursor); - } - - self.input.update(cx, |input, _cx| { - input.last_layout = Some(line); - input.last_bounds = Some(bounds); - }); - } -} - -impl Render for TextInput { - fn render(&mut self, _window: &mut Window, cx: &mut Context) -> impl IntoElement { - div() - .flex() - .key_context("TextInput") - .track_focus(&self.focus_handle(cx)) - .cursor(CursorStyle::IBeam) - .on_action(cx.listener(Self::backspace)) - .on_action(cx.listener(Self::delete)) - .on_action(cx.listener(Self::left)) - .on_action(cx.listener(Self::right)) - .on_action(cx.listener(Self::select_left)) - .on_action(cx.listener(Self::select_right)) - .on_action(cx.listener(Self::select_all)) - .on_action(cx.listener(Self::home)) - .on_action(cx.listener(Self::end)) - .on_action(cx.listener(Self::show_character_palette)) - .on_action(cx.listener(Self::paste)) - .on_action(cx.listener(Self::cut)) - .on_action(cx.listener(Self::copy)) - .on_mouse_down(MouseButton::Left, cx.listener(Self::on_mouse_down)) - .on_mouse_up(MouseButton::Left, cx.listener(Self::on_mouse_up)) - .on_mouse_up_out(MouseButton::Left, cx.listener(Self::on_mouse_up)) - .on_mouse_move(cx.listener(Self::on_mouse_move)) - .bg(rgb(0xeeeeee)) - .line_height(px(30.)) - .text_size(px(24.)) - .child( - div() - .h(px(30. + 4. * 2.)) - .w_full() - .p(px(4.)) - .bg(white()) - .child(TextElement { input: cx.entity() }), - ) - } -} - -impl Focusable for TextInput { - fn focus_handle(&self, _: &App) -> FocusHandle { - self.focus_handle.clone() - } -} - -struct InputExample { - text_input: Entity, - recent_keystrokes: Vec, - focus_handle: FocusHandle, -} - -impl Focusable for InputExample { - fn focus_handle(&self, _: &App) -> FocusHandle { - self.focus_handle.clone() - } -} - -impl InputExample { - fn on_reset_click(&mut self, _: &MouseUpEvent, _window: &mut Window, cx: &mut Context) { - self.recent_keystrokes.clear(); - self.text_input - .update(cx, |text_input, _cx| text_input.reset()); - cx.notify(); - } -} - -impl Render for InputExample { - fn render(&mut self, _window: &mut Window, cx: &mut Context) -> impl IntoElement { - div() - .bg(rgb(0xaaaaaa)) - .track_focus(&self.focus_handle(cx)) - .flex() - .flex_col() - .size_full() - .child( - div() - .bg(white()) - .border_b_1() - .border_color(black()) - .flex() - .flex_row() - .justify_between() - .child(format!("Keyboard {}", cx.keyboard_layout().name())) - .child( - div() - .border_1() - .border_color(black()) - .px_2() - .bg(yellow()) - .child("Reset") - .hover(|style| { - style - .bg(yellow().blend(opaque_grey(0.5, 0.5))) - .cursor_pointer() - }) - .on_mouse_up(MouseButton::Left, cx.listener(Self::on_reset_click)), - ), - ) - .child(self.text_input.clone()) - .children(self.recent_keystrokes.iter().rev().map(|ks| { - format!( - "{:} {}", - ks.unparse(), - if let Some(key_char) = ks.key_char.as_ref() { - format!("-> {:?}", key_char) - } else { - "".to_owned() - } - ) - })) - } -} - -fn main() { - gpui_platform::application().run(|cx: &mut App| { - let bounds = Bounds::centered(None, size(px(300.0), px(300.0)), cx); - cx.bind_keys([ - KeyBinding::new("backspace", Backspace, None), - KeyBinding::new("delete", Delete, None), - KeyBinding::new("left", Left, None), - KeyBinding::new("right", Right, None), - KeyBinding::new("shift-left", SelectLeft, None), - KeyBinding::new("shift-right", SelectRight, None), - KeyBinding::new("cmd-a", SelectAll, None), - KeyBinding::new("cmd-v", Paste, None), - KeyBinding::new("cmd-c", Copy, None), - KeyBinding::new("cmd-x", Cut, None), - KeyBinding::new("home", Home, None), - KeyBinding::new("end", End, None), - KeyBinding::new("ctrl-cmd-space", ShowCharacterPalette, None), - ]); - - let window = cx - .open_window( - WindowOptions { - window_bounds: Some(WindowBounds::Windowed(bounds)), - ..Default::default() - }, - |_, cx| { - let text_input = cx.new(|cx| TextInput { - focus_handle: cx.focus_handle(), - content: "".into(), - placeholder: "Type here...".into(), - selected_range: 0..0, - selection_reversed: false, - marked_range: None, - last_layout: None, - last_bounds: None, - is_selecting: false, - }); - cx.new(|cx| InputExample { - text_input, - recent_keystrokes: vec![], - focus_handle: cx.focus_handle(), - }) - }, - ) - .unwrap(); - let view = window.update(cx, |_, _, cx| cx.entity()).unwrap(); - cx.observe_keystrokes(move |ev, _, cx| { - view.update(cx, |view, cx| { - view.recent_keystrokes.push(ev.keystroke.clone()); - cx.notify(); - }) - }) - .detach(); - cx.on_keyboard_layout_change({ - move |cx| { - window.update(cx, |_, _, cx| cx.notify()).ok(); - } - }) - .detach(); - - window - .update(cx, |view, window, cx| { - window.focus(&view.text_input.focus_handle(cx), cx); - cx.activate(true); - }) - .unwrap(); - cx.on_action(|_: &Quit, cx| cx.quit()); - cx.bind_keys([KeyBinding::new("cmd-q", Quit, None)]); - }); -}