From d5b31917388921ae25ef5c9324f3e2e06f6d9b82 Mon Sep 17 00:00:00 2001 From: temportalflux Date: Fri, 10 Jul 2026 18:16:00 -0400 Subject: [PATCH] add simpler api for configuring the caret blink interval via element --- crates/gpui_elements/src/editable_text.rs | 17 +++++++++++-- .../gpui_elements/src/editable_text/caret.rs | 25 +++++++++++++------ .../src/editable_text/element.rs | 23 +++++++++++++++-- 3 files changed, 53 insertions(+), 12 deletions(-) diff --git a/crates/gpui_elements/src/editable_text.rs b/crates/gpui_elements/src/editable_text.rs index 29650020ba..1abe4f524d 100644 --- a/crates/gpui_elements/src/editable_text.rs +++ b/crates/gpui_elements/src/editable_text.rs @@ -115,7 +115,20 @@ //! # } //! ``` //! -//! To use a blinking caret, construct a caret entity with a blinking interval when constructing the state: +//! To use a blinking caret, you can use one of the templated functions: +//! ``` +//! # use gpui::{prelude::*, App, Entity, Window, AppContext, ElementId}; +//! # fn test(window: &mut Window, cx: &mut App) -> gpui_elements::editable_text::EditableTextElement { +//! use gpui_elements::editable_text::{text_input}; +//! let id = ElementId::from("my_input"); +//! text_input(id) +//! .caret_blink_interval_500ms() +//! // or use the parameterized one, e.g. 200ms +//! .caret_blink_interval(std::time::Duration::from_millis(200)) +//! # } +//! ``` +//! +//! or construct a caret entity with a blinking interval when constructing the state: //! ``` //! # use gpui::{prelude::*, App, Entity, Window, AppContext, ElementId}; //! # fn test(window: &mut Window, cx: &mut App) -> gpui_elements::editable_text::EditableTextElement { @@ -127,7 +140,7 @@ //! // Ensure the caret exists, linked to the input element by id. //! window.use_keyed_state(id.clone(), cx, |window, cx| { //! // using the default interval of 500ms -//! let mut caret = Caret::default().blink_interval_default(); +//! let mut caret = Caret::default().with_blink_interval_500ms(); //! // ensures the caret receives events from the input state during typing & other actions //! caret.subscribe_to(&state, cx); //! caret diff --git a/crates/gpui_elements/src/editable_text/caret.rs b/crates/gpui_elements/src/editable_text/caret.rs index 62a8181e35..592d87c755 100644 --- a/crates/gpui_elements/src/editable_text/caret.rs +++ b/crates/gpui_elements/src/editable_text/caret.rs @@ -3,7 +3,7 @@ use smallvec::SmallVec; use std::time::Duration; /// Default interval for caret blinking (500ms). -pub const DEFAULT_BLINK_INTERVAL: Duration = Duration::from_millis(500); +pub const BLINK_INTERVAL_500MS: Duration = Duration::from_millis(500); /// Events emitted that the [`Caret`] listens to. pub enum CaretNotify { @@ -44,19 +44,28 @@ impl Default for Caret { } impl Caret { - /// Sets the blinking interval of the caret to the global "default". - /// The true default of the caret is "do not blink". - pub fn blink_interval_default(mut self) -> Self { - self.interval = DEFAULT_BLINK_INTERVAL; - self + /// Returns the duration of the current blink interval + pub fn blink_interval(&self) -> Duration { + self.interval } /// Sets the blinking interval of the caret. - pub fn blink_interval(mut self, interval: Duration) -> Self { + pub fn set_blink_interval(&mut self, interval: Duration) { self.interval = interval; + } + + /// Sets the blinking interval of the caret. + pub fn with_blink_interval(mut self, interval: Duration) -> Self { + self.set_blink_interval(interval); self } + /// Sets the blinking interval of the caret to the global "default". + /// The true default of the caret is "do not blink". + pub fn with_blink_interval_500ms(self) -> Self { + self.with_blink_interval(BLINK_INTERVAL_500MS) + } + /// Listens for CaretNotify events on an entity (e.g. [`EditableTextState`]). pub fn subscribe_to(&mut self, emitter: &Entity, cx: &mut Context) where @@ -88,7 +97,7 @@ impl Caret { // Caret has no blinking interval, it is always visible if self.interval.is_zero() { - return true; + return is_focused; } match (is_focused, was_focused) { diff --git a/crates/gpui_elements/src/editable_text/element.rs b/crates/gpui_elements/src/editable_text/element.rs index d405302f49..d9cd6d247c 100644 --- a/crates/gpui_elements/src/editable_text/element.rs +++ b/crates/gpui_elements/src/editable_text/element.rs @@ -1,5 +1,5 @@ use crate::editable_text::{ - Caret, EditableTextState, + BLINK_INTERVAL_500MS, Caret, EditableTextState, actions::{DEFAULT_INPUT_CONTEXT, EditableTextActionElement, EditableTextActionHandler}, layout::{EditableTextLayoutResult, EditableTextLayoutState, TextLineSegment}, }; @@ -12,7 +12,7 @@ use gpui::{ px, size, }; use smallvec::SmallVec; -use std::{cell::RefCell, ops::Range, rc::Rc, sync::Arc}; +use std::{cell::RefCell, ops::Range, rc::Rc, sync::Arc, time::Duration}; const CARET_RENDER_WIDTH: f32 = 2.0; @@ -29,6 +29,7 @@ pub fn editable_text(id: impl Into) -> EditableTextElement { placeholder: None, accepts_input: true, colors: EditableTextColors::default(), + caret_blink_interval: None, }; this.interactivity.element_id = Some(id.into()); @@ -63,6 +64,7 @@ pub struct EditableTextElement { placeholder: Option, accepts_input: bool, colors: EditableTextColors, + caret_blink_interval: Option, } /// EditableText styling that goes beyond what Style/StyleRefinement supports @@ -140,6 +142,17 @@ impl EditableTextElement { self } + /// Sets the blinking interval of the caret. + pub fn caret_blink_interval(mut self, duration: Duration) -> Self { + self.caret_blink_interval = Some(duration); + self + } + + /// Sets the blinking interval of the caret to 500ms + pub fn caret_blink_interval_500ms(self) -> Self { + self.caret_blink_interval(BLINK_INTERVAL_500MS) + } + /// Sets the color of the placeholder text which is rendered when the element's stored text is empty. /// /// Cannot be refined via [`StyleRefinement`](gpui::StyleRefinement) due to limitations in the fields of [`Style`](gpui::Style). @@ -256,6 +269,12 @@ impl Element for EditableTextElement { let entity = self.find_or_create_state(window, cx); let caret = self.find_or_create_caret(&entity, window, cx); + if let Some(duration) = self.caret_blink_interval.take() + && caret.read(cx).blink_interval() != duration + { + caret.update(cx, |caret, _cx| caret.set_blink_interval(duration)); + } + // Read new state information from the underlying entity. // Block-wrapped so that the state being read is dropped before continuing. let (prelayout, next_scroll_offset) = {