gpui and gpui_platform now come from Zed's repo by git at 1a28cff, the code gpui-pre 0.3.7 republishes, so the gallery can later build for the browser. gpui takes stacker, as 0.2.2 always did, and gpui_platform takes font-kit, without which macOS draws no text; runtime_shaders moves to gpui_platform. The API moved: focus, blur and Tab steps take cx, Corner is Anchor, shaped lines paint with an alignment, defer_draw takes a content mask, the inspector registers a factory, async updates return their value, and wrapped lines are shared, not cloned. Updates through a window context go through the window, so a closed window still ends them as it did. Where the new gpui behaves differently, Ely keeps its old intent. Holds and reveals under a resting pointer survive typing through gpui's input-modality-independent hover; previews the keyboard also moves keep the default, and the calendar's leave ignores a key press. Enter reaches a button inside the code editor, since gpui now arms a key click on the press. The event logger skips a lone modifier. A video and an image grid keep their shape in a flex column under taffy 0.13, as a picture does. AGENTS.md records the new stack and the rules this port changes; the rest of the old gpui and taffy rules are re-tested in T44c.
134 lines
4.4 KiB
Rust
134 lines
4.4 KiB
Rust
use std::ops::Range;
|
|
|
|
use gpui::{
|
|
App, ClipboardItem, CursorStyle, ElementId, FocusHandle, HighlightStyle, InteractiveElement,
|
|
IntoElement, MouseButton, ParentElement, RenderOnce, SharedString, Styled, StyledText,
|
|
TextLayout, Window, div,
|
|
};
|
|
|
|
use crate::theme::ActiveTheme;
|
|
|
|
struct Selection {
|
|
text: SharedString,
|
|
focus: FocusHandle,
|
|
anchor: usize,
|
|
head: usize,
|
|
dragging: bool,
|
|
}
|
|
|
|
impl Selection {
|
|
fn range(&self) -> Range<usize> {
|
|
self.anchor.min(self.head)..self.anchor.max(self.head)
|
|
}
|
|
}
|
|
|
|
fn index_at(layout: &TextLayout, position: gpui::Point<gpui::Pixels>) -> usize {
|
|
match layout.index_for_position(position) {
|
|
Ok(ix) | Err(ix) => ix,
|
|
}
|
|
}
|
|
|
|
/// Read-only text the pointer can select and copy.
|
|
#[derive(IntoElement)]
|
|
pub struct SelectableText {
|
|
id: ElementId,
|
|
text: SharedString,
|
|
}
|
|
|
|
impl SelectableText {
|
|
pub fn new(id: impl Into<ElementId>, text: impl Into<SharedString>) -> Self {
|
|
Self {
|
|
id: id.into(),
|
|
text: text.into(),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl RenderOnce for SelectableText {
|
|
fn render(self, window: &mut Window, cx: &mut App) -> impl IntoElement {
|
|
let state = window.use_keyed_state(self.id.clone(), cx, |_, cx| Selection {
|
|
text: self.text.clone(),
|
|
focus: cx.focus_handle(),
|
|
anchor: 0,
|
|
head: 0,
|
|
dragging: false,
|
|
});
|
|
if state.read(cx).text != self.text {
|
|
state.update(cx, |selection, _| {
|
|
selection.text = self.text.clone();
|
|
selection.anchor = 0;
|
|
selection.head = 0;
|
|
});
|
|
}
|
|
let (focus, range) = {
|
|
let selection = state.read(cx);
|
|
(selection.focus.clone(), selection.range())
|
|
};
|
|
let highlight = HighlightStyle {
|
|
background_color: Some(cx.theme().colors.selection),
|
|
..Default::default()
|
|
};
|
|
let styled = StyledText::new(self.text.clone())
|
|
.with_highlights((!range.is_empty()).then_some((range, highlight)));
|
|
let layout = styled.layout().clone();
|
|
let (down, drag, up, keys) = (state.clone(), state.clone(), state.clone(), state);
|
|
let (down_layout, drag_layout) = (layout.clone(), layout);
|
|
let text = self.text;
|
|
|
|
div()
|
|
.id(self.id)
|
|
.track_focus(&focus)
|
|
.cursor(CursorStyle::IBeam)
|
|
.on_mouse_down(MouseButton::Left, move |event, window, cx| {
|
|
let ix = index_at(&down_layout, event.position);
|
|
down.update(cx, |selection, cx| {
|
|
window.focus(&selection.focus, cx);
|
|
if !event.modifiers.shift {
|
|
selection.anchor = ix;
|
|
}
|
|
selection.head = ix;
|
|
selection.dragging = true;
|
|
cx.notify();
|
|
});
|
|
})
|
|
.on_mouse_move(move |event, _, cx| {
|
|
drag.update(cx, |selection, cx| {
|
|
if selection.dragging && event.dragging() {
|
|
selection.head = index_at(&drag_layout, event.position);
|
|
cx.notify();
|
|
}
|
|
})
|
|
})
|
|
.on_mouse_up(MouseButton::Left, {
|
|
let up = up.clone();
|
|
move |_, _, cx| up.update(cx, |selection, _| selection.dragging = false)
|
|
})
|
|
.on_mouse_up_out(MouseButton::Left, move |_, _, cx| {
|
|
up.update(cx, |selection, _| selection.dragging = false)
|
|
})
|
|
.on_key_down(move |event, _, cx| {
|
|
let stroke = &event.keystroke;
|
|
if !stroke.modifiers.secondary() {
|
|
return;
|
|
}
|
|
match stroke.key.as_str() {
|
|
"c" => {
|
|
let range = keys.read(cx).range();
|
|
if !range.is_empty() {
|
|
cx.write_to_clipboard(ClipboardItem::new_string(
|
|
text[range].to_string(),
|
|
));
|
|
}
|
|
}
|
|
"a" => keys.update(cx, |selection, cx| {
|
|
selection.anchor = 0;
|
|
selection.head = text.len();
|
|
cx.notify();
|
|
}),
|
|
_ => {}
|
|
}
|
|
})
|
|
.child(styled)
|
|
}
|
|
}
|