Detailed changes
@@ -2,17 +2,14 @@
//! blink state, and keyboard handling.
//!
//! Also contains `ExampleEditorText`, the low-level custom `Element` that shapes text
-//! and paints the cursor, and `ExampleEditorView`, a cached `View` wrapper that
-//! automatically pairs an `ExampleEditor` entity with its `ExampleEditorText` element.
-
-use std::hash::Hash;
+//! and paints the cursor.
use std::ops::Range;
use std::time::Duration;
use gpui::{
App, Bounds, Context, ElementInputHandler, Entity, EntityInputHandler, FocusHandle, Focusable,
- Hsla, IntoViewElement, LayoutId, PaintQuad, Pixels, ShapedLine, SharedString, Task, TextRun,
- UTF16Selection, Window, fill, hsla, point, prelude::*, px, relative, size,
+ LayoutId, PaintQuad, Pixels, ShapedLine, SharedString, Task, TextRun, UTF16Selection, Window,
+ fill, hsla, point, prelude::*, px, relative, size,
};
use unicode_segmentation::*;
@@ -263,7 +260,6 @@ impl EntityInputHandler for ExampleEditor {
struct ExampleEditorText {
editor: Entity<ExampleEditor>,
- text_color: Hsla,
}
struct ExampleEditorTextPrepaintState {
@@ -272,8 +268,8 @@ struct ExampleEditorTextPrepaintState {
}
impl ExampleEditorText {
- pub fn new(editor: Entity<ExampleEditor>, text_color: Hsla) -> Self {
- Self { editor, text_color }
+ pub fn new(editor: Entity<ExampleEditor>) -> Self {
+ Self { editor }
}
}
@@ -328,6 +324,7 @@ impl Element for ExampleEditorText {
let is_focused = editor.focus_handle.is_focused(window);
let style = window.text_style();
+ let text_color = style.color;
let font_size = style.font_size.to_pixels(window.rem_size());
let line_height = window.line_height();
@@ -356,7 +353,7 @@ impl Element for ExampleEditorText {
let run = TextRun {
len: text.len(),
font: style.font(),
- color: self.text_color,
+ color: text_color,
background_color: None,
underline: None,
strikethrough: None,
@@ -381,7 +378,7 @@ impl Element for ExampleEditorText {
),
size(px(1.5), line_height),
),
- self.text_color,
+ text_color,
))
} else if is_focused && cursor_visible && is_placeholder {
Some(fill(
@@ -389,7 +386,7 @@ impl Element for ExampleEditorText {
point(bounds.left(), bounds.top()),
size(px(1.5), line_height),
),
- self.text_color,
+ text_color,
))
} else {
None
@@ -447,41 +444,12 @@ fn cursor_line_and_offset(content: &str, cursor: usize) -> (usize, usize) {
(line_index, cursor - line_start)
}
-// ---------------------------------------------------------------------------
-// ExampleEditorView — a cached View that pairs an ExampleEditor entity with ExampleEditorText
-// ---------------------------------------------------------------------------
-
-/// A simple cached view that renders an `ExampleEditor` entity via the `ExampleEditorText`
-/// custom element. Use this when you want a bare editor display with automatic
-/// caching and no extra chrome.
-#[derive(IntoViewElement, Hash)]
-pub struct ExampleEditorView {
- editor: Entity<ExampleEditor>,
- text_color: Hsla,
-}
-
-impl ExampleEditorView {
- pub fn new(editor: Entity<ExampleEditor>) -> Self {
- Self {
- editor,
- text_color: hsla(0., 0., 0.1, 1.),
- }
- }
-
- pub fn text_color(mut self, color: Hsla) -> Self {
- self.text_color = color;
- self
- }
-}
-
-impl gpui::View for ExampleEditorView {
- type Entity = ExampleEditor;
-
- fn entity(&self) -> Option<Entity<ExampleEditor>> {
- Some(self.editor.clone())
- }
-
- fn render(self, _window: &mut Window, _cx: &mut App) -> impl IntoElement {
- ExampleEditorText::new(self.editor, self.text_color)
+impl gpui::EntityView for ExampleEditor {
+ fn render(
+ &mut self,
+ _window: &mut Window,
+ cx: &mut Context<ExampleEditor>,
+ ) -> impl IntoElement {
+ ExampleEditorText::new(cx.entity().clone())
}
}
@@ -9,12 +9,11 @@ use std::time::Duration;
use gpui::{
Animation, AnimationExt as _, App, BoxShadow, CursorStyle, Entity, Hsla, IntoViewElement,
- Pixels, SharedString, StyleRefinement, Window, bounce, div, ease_in_out, hsla, point,
- prelude::*, px, white,
+ Pixels, SharedString, StyleRefinement, ViewElement, Window, bounce, div, ease_in_out, hsla,
+ point, prelude::*, px, white,
};
use crate::example_editor::ExampleEditor;
-use crate::example_editor::ExampleEditorView;
use crate::{Backspace, Delete, End, Enter, Home, Left, Right};
struct FlashState {
@@ -55,7 +54,7 @@ impl gpui::View for ExampleInput {
Some(self.editor.clone())
}
- fn style(&self) -> Option<StyleRefinement> {
+ fn cache_style(&mut self, _window: &mut Window, _cx: &mut App) -> Option<StyleRefinement> {
let mut style = StyleRefinement::default();
if let Some(w) = self.width {
style.size.width = Some(w.into());
@@ -154,7 +153,7 @@ impl gpui::View for ExampleInput {
.line_height(px(20.))
.text_size(px(14.))
.text_color(text_color)
- .child(ExampleEditorView::new(editor).text_color(text_color));
+ .child(ViewElement::new(editor));
if count > 0 {
base.with_animation(
@@ -5,12 +5,11 @@
//! components with different props and layouts.
use gpui::{
- App, BoxShadow, CursorStyle, Entity, Hsla, IntoViewElement, StyleRefinement, Window, div, hsla,
- point, prelude::*, px, white,
+ App, BoxShadow, CursorStyle, Entity, Hsla, IntoViewElement, StyleRefinement, ViewElement,
+ Window, div, hsla, point, prelude::*, px, white,
};
use crate::example_editor::ExampleEditor;
-use crate::example_editor::ExampleEditorView;
use crate::{Backspace, Delete, End, Enter, Home, Left, Right};
#[derive(Hash, IntoViewElement)]
@@ -42,7 +41,7 @@ impl gpui::View for ExampleTextArea {
Some(self.editor.clone())
}
- fn style(&self) -> Option<StyleRefinement> {
+ fn cache_style(&mut self, _window: &mut Window, _cx: &mut App) -> Option<StyleRefinement> {
let row_height = px(20.);
let box_height = row_height * self.rows as f32 + px(16.);
let mut style = StyleRefinement::default();
@@ -129,6 +128,6 @@ impl gpui::View for ExampleTextArea {
.line_height(row_height)
.text_size(px(14.))
.text_color(text_color)
- .child(ExampleEditorView::new(editor).text_color(text_color))
+ .child(ViewElement::new(editor))
}
}
@@ -383,17 +383,14 @@ pub trait View: 'static + Sized + Hash {
/// When `Some`, the view element will be cached using the given style for its outer layout.
/// The default returns a full-size style refinement (`width: 100%, height: 100%`).
/// Return `None` to disable caching.
- fn style(&self) -> Option<StyleRefinement> {
+ fn cache_style(&mut self, _window: &mut Window, _cx: &mut App) -> Option<StyleRefinement> {
Some(StyleRefinement::default().size_full())
}
}
/// A stateless component that renders an element tree without an entity.
///
-/// This is the `View` equivalent of [`RenderOnce`](crate::RenderOnce). Types that
-/// implement `ComponentView` get a blanket implementation of [`View`] with
-/// `entity()` returning `None` and `style()` returning `None` — meaning no
-/// reactive boundary, no caching, just subtree isolation via the type name.
+/// This is the `View` equivalent of [`RenderOnce`](crate::RenderOnce).
///
/// # Example
///
@@ -413,6 +410,11 @@ pub trait ComponentView: 'static + Sized + Hash {
/// Render this component into an element tree. Takes ownership of self,
/// consuming the component props.
fn render(self, window: &mut Window, cx: &mut App) -> impl IntoElement;
+
+ /// Indicate that this view should be cached
+ fn cache_style(&mut self, _window: &mut Window, _cx: &mut App) -> Option<StyleRefinement> {
+ None
+ }
}
impl<T: ComponentView> View for T {
@@ -422,15 +424,53 @@ impl<T: ComponentView> View for T {
None
}
+ #[inline]
fn render(self, window: &mut Window, cx: &mut App) -> impl IntoElement {
ComponentView::render(self, window, cx)
}
- fn style(&self) -> Option<StyleRefinement> {
+ #[inline]
+ fn cache_style(&mut self, window: &mut Window, cx: &mut App) -> Option<StyleRefinement> {
+ ComponentView::cache_style(self, window, cx)
+ }
+}
+
+/// For entities that require only one kind of rendering, this trait provides a simplified interface.
+/// Equivalent to the `Render` trait
+pub trait EntityView: 'static + Sized {
+ /// Render this entity into the element tree.
+ fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement;
+ /// Indicate that this entity should be cached when using it as an element.
+ /// When using this method, the entity's previous layout and paint will be recycled from the previous frame if [Context::notify] has not been called since it was rendered.
+ fn cache_style(
+ &mut self,
+ _window: &mut Window,
+ _cx: &mut Context<Self>,
+ ) -> Option<StyleRefinement> {
None
}
}
+impl<T: EntityView> View for Entity<T> {
+ type Entity = T;
+
+ fn entity(&self) -> Option<Entity<Self::Entity>> {
+ Some(self.clone())
+ }
+
+ #[inline]
+ fn render(self, window: &mut Window, cx: &mut App) -> impl IntoElement {
+ self.update(cx, |this, cx| {
+ EntityView::render(this, window, cx).into_any_element()
+ })
+ }
+
+ #[inline]
+ fn cache_style(&mut self, window: &mut Window, cx: &mut App) -> Option<StyleRefinement> {
+ self.update(cx, |this, cx| EntityView::cache_style(this, window, cx))
+ }
+}
+
/// An element that wraps a [`View`], creating a reactive boundary in the element tree.
///
/// This is the stateful counterpart to [`Component`](crate::Component) — where `Component<C>`
@@ -462,18 +502,16 @@ impl<V: View> ViewElement<V> {
/// Create a new `ViewElement` wrapping the given [`View`].
///
/// Use this in your [`IntoElement`] implementation.
- #[track_caller]
pub fn new(view: V) -> Self {
use std::hash::Hasher;
let entity_id = view.entity().map(|e| e.entity_id());
- let cached_style = view.style();
let mut hasher = std::collections::hash_map::DefaultHasher::new();
view.hash(&mut hasher);
let props_hash = hasher.finish();
ViewElement {
entity_id,
props_hash,
- cached_style,
+ cached_style: None,
view: Some(view),
#[cfg(debug_assertions)]
source: core::panic::Location::caller(),
@@ -526,6 +564,12 @@ impl<V: View> Element for ViewElement<V> {
window: &mut Window,
cx: &mut App,
) -> (LayoutId, Self::RequestLayoutState) {
+ if self.cached_style.is_none() {
+ if let Some(view) = self.view.as_mut() {
+ self.cached_style = view.cache_style(window, cx);
+ }
+ }
+
if let Some(entity_id) = self.entity_id {
// Stateful path: create a reactive boundary.
window.with_rendered_view(entity_id, |window| {