use gpui::*;
use gpui::prelude::FluentBuilder;
use std::sync::{Arc, Mutex};
#[derive(Clone)]
struct TextInputLayout {
bounds: Bounds<Pixels>,
shaped_line: ShapedLine,
}
struct TextInputElement {
entity: Entity<TextInput>,
}
impl IntoElement for TextInputElement {
type Element = Self;
fn into_element(self) -> Self::Element {
self
}
}
impl Element for TextInputElement {
type RequestLayoutState = ();
type PrepaintState = ();
fn id(&self) -> Option<ElementId> {
None
}
fn source_location(&self) -> Option<&'static core::panic::Location<'static>> {
None
}
fn request_layout(
&mut self,
_id: Option<&GlobalElementId>,
_inspector_id: Option<&InspectorElementId>,
window: &mut Window,
cx: &mut App,
) -> (LayoutId, Self::RequestLayoutState) {
let mut style = Style::default();
style.size.width = px(0.0).into();
style.size.height = px(0.0).into();
(window.request_layout(style, [], cx), ())
}
fn prepaint(
&mut self,
_id: Option<&GlobalElementId>,
_inspector_id: Option<&InspectorElementId>,
_bounds: Bounds<Pixels>,
_request_layout: &mut Self::RequestLayoutState,
_window: &mut Window,
_cx: &mut App,
) -> Self::PrepaintState {
}
fn paint(
&mut self,
_id: Option<&GlobalElementId>,
_inspector_id: Option<&InspectorElementId>,
bounds: Bounds<Pixels>,
_request_layout: &mut Self::RequestLayoutState,
_prepaint: &mut Self::PrepaintState,
window: &mut Window,
cx: &mut App,
) {
let focus_handle = self.entity.read(cx).focus_handle.clone();
window.handle_input(
&focus_handle,
ElementInputHandler::new(bounds, self.entity.clone()),
cx,
);
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum PasswordMaskMode {
All,
Partial {
prefix_len: usize,
suffix_len: usize,
},
}
impl Default for PasswordMaskMode {
fn default() -> Self {
Self::All
}
}
#[derive(Clone, Debug)]
pub enum TextInputEvent {
Change(String),
Submit(String),
Focus,
Blur,
}
pub struct TextInput {
value: String,
cursor_position: usize,
selection_start: Option<usize>,
selection_end: Option<usize>,
placeholder: String,
focus_handle: FocusHandle,
disabled: bool,
is_password: bool,
password_visible: bool,
password_mask_mode: PasswordMaskMode,
max_length: Option<usize>,
validator: Option<Arc<dyn Fn(&str) -> bool>>,
blink_epoch: usize,
cursor_visible: bool,
_blink_task: Option<Task<()>>,
is_dragging: bool,
last_layout: Option<TextInputLayout>,
marked_range: Option<std::ops::Range<usize>>,
show_border: bool,
custom_bg_color: Option<Rgba>,
custom_border_color: Option<Rgba>,
}
impl TextInput {
pub fn new(cx: &mut Context<Self>) -> Self {
Self {
value: String::new(),
cursor_position: 0,
selection_start: None,
selection_end: None,
placeholder: String::new(),
focus_handle: cx.focus_handle(),
disabled: false,
is_password: false,
password_visible: false,
password_mask_mode: PasswordMaskMode::All,
max_length: None,
validator: None,
blink_epoch: 0,
cursor_visible: true,
_blink_task: None,
is_dragging: false,
last_layout: None,
marked_range: None,
show_border: true,
custom_bg_color: None,
custom_border_color: None,
}
}
pub fn placeholder(mut self, placeholder: impl Into<String>) -> Self {
self.placeholder = placeholder.into();
self
}
pub fn value(mut self, value: impl Into<String>) -> Self {
self.value = value.into();
self.cursor_position = self.value.len();
self.selection_start = None;
self.selection_end = None;
self
}
pub fn disabled(mut self, disabled: bool) -> Self {
self.disabled = disabled;
self
}
pub fn password(mut self, is_password: bool) -> Self {
self.is_password = is_password;
self
}
pub fn toggle_password_visibility(&mut self, cx: &mut Context<Self>) {
if self.is_password {
self.password_visible = !self.password_visible;
cx.notify();
}
}
pub fn show_password(mut self, visible: bool) -> Self {
self.password_visible = visible;
self
}
pub fn password_mask_mode(mut self, mode: PasswordMaskMode) -> Self {
self.password_mask_mode = mode;
self
}
pub fn max_length(mut self, max_length: usize) -> Self {
self.max_length = Some(max_length);
self
}
pub fn validator(mut self, validator: impl Fn(&str) -> bool + 'static) -> Self {
self.validator = Some(Arc::new(validator));
self
}
pub fn no_border(mut self) -> Self {
self.show_border = false;
self
}
pub fn bg_color(mut self, color: Rgba) -> Self {
self.custom_bg_color = Some(color);
self
}
pub fn border_color(mut self, color: Rgba) -> Self {
self.custom_border_color = Some(color);
self
}
pub fn transparent(mut self) -> Self {
self.custom_bg_color = Some(rgba(0x00000000));
self
}
pub fn get_value(&self) -> &str {
&self.value
}
pub fn set_value(&mut self, value: String, cx: &mut Context<Self>) {
if let Some(ref validator) = self.validator {
if !validator(&value) {
return;
}
}
if let Some(max_len) = self.max_length {
if value.len() > max_len {
return;
}
}
self.value = value.clone();
self.cursor_position = self.value.len();
self.selection_start = None;
self.selection_end = None;
cx.emit(TextInputEvent::Change(value));
cx.notify();
}
pub fn clear(&mut self, cx: &mut Context<Self>) {
self.value.clear();
self.cursor_position = 0;
self.selection_start = None;
self.selection_end = None;
cx.emit(TextInputEvent::Change(String::new()));
cx.notify();
}
pub fn focus(&self, window: &mut Window) {
self.focus_handle.focus(window);
}
pub fn select_all(&mut self, cx: &mut Context<Self>) {
if !self.value.is_empty() {
self.selection_start = Some(0);
self.selection_end = Some(self.value.len());
cx.notify();
}
}
fn clear_selection(&mut self) {
self.selection_start = None;
self.selection_end = None;
}
fn has_selection(&self) -> bool {
self.selection_start.is_some() && self.selection_end.is_some()
}
fn next_blink_epoch(&mut self) -> usize {
self.blink_epoch += 1;
self.blink_epoch
}
fn start_blinking(&mut self, epoch: usize, cx: &mut Context<Self>) {
if epoch == self.blink_epoch {
self.cursor_visible = !self.cursor_visible;
cx.notify();
let next_epoch = self.next_blink_epoch();
let task = cx.spawn(async move |this, cx| {
cx.background_spawn(async move {
std::thread::sleep(std::time::Duration::from_millis(530));
}).await;
_ = this.update(cx, |this, cx| {
this.start_blinking(next_epoch, cx);
});
});
self._blink_task = Some(task);
}
}
fn pause_blinking(&mut self, cx: &mut Context<Self>) {
self.cursor_visible = true;
cx.notify();
let epoch = self.next_blink_epoch();
let task = cx.spawn(async move |this, cx| {
cx.background_spawn(async move {
std::thread::sleep(std::time::Duration::from_millis(530));
}).await;
_ = this.update(cx, |this, cx| {
this.start_blinking(epoch, cx);
});
});
self._blink_task = Some(task);
}
fn index_for_mouse_position(&self, position: Point<Pixels>) -> usize {
if self.value.is_empty() {
return 0;
}
let Some(layout) = &self.last_layout else {
return self.value.len();
};
if position.y < layout.bounds.top() {
return 0;
}
if position.y > layout.bounds.bottom() {
return self.value.len();
}
let relative_x = position.x - layout.bounds.left();
let byte_index = layout.shaped_line.closest_index_for_x(relative_x);
if self.is_password && !self.password_visible {
match self.password_mask_mode {
PasswordMaskMode::All => {
let bullet_len = "•".len(); let char_index = byte_index / bullet_len;
self.value.char_indices().nth(char_index).map(|(i, _)| i).unwrap_or(self.value.len())
}
PasswordMaskMode::Partial { prefix_len, suffix_len } => {
let chars: Vec<char> = self.value.chars().collect();
let len = chars.len();
if len <= prefix_len + suffix_len {
let bullet_len = "•".len();
let char_index = byte_index / bullet_len;
self.value.char_indices().nth(char_index).map(|(i, _)| i).unwrap_or(self.value.len())
} else {
let mut display_pos = 0;
let mut value_pos = 0;
for (i, ch) in chars.iter().enumerate() {
let ch_bytes = ch.len_utf8();
if i < prefix_len {
if display_pos + ch_bytes > byte_index {
break;
}
display_pos += ch_bytes;
value_pos += ch_bytes;
} else if i >= len - suffix_len {
if display_pos + ch_bytes > byte_index {
break;
}
display_pos += ch_bytes;
value_pos += ch_bytes;
} else {
let bullet_len = "•".len();
if display_pos + bullet_len > byte_index {
break;
}
display_pos += bullet_len;
value_pos += ch_bytes;
}
}
value_pos.min(self.value.len())
}
}
}
} else {
byte_index.min(self.value.len())
}
}
fn build_text_runs(&self, font: Font, _font_size: Pixels) -> (String, Vec<TextRun>) {
let display_text = if self.is_password && !self.password_visible {
self.build_password_display_text()
} else {
self.value.clone()
};
if !self.has_selection() {
return (
display_text.clone(),
vec![TextRun {
len: display_text.len(),
font,
color: rgb(0x333333).into(),
background_color: None,
underline: None,
strikethrough: None,
}],
);
}
let (mut sel_start, mut sel_end) = if let (Some(start), Some(end)) =
(self.selection_start, self.selection_end)
{
if start <= end {
(start, end)
} else {
(end, start)
}
} else {
(0, 0)
};
sel_start = sel_start.min(self.value.len());
sel_end = sel_end.min(self.value.len());
while sel_start > 0 && !self.value.is_char_boundary(sel_start) {
sel_start -= 1;
}
while sel_end > 0 && sel_end < self.value.len() && !self.value.is_char_boundary(sel_end) {
sel_end += 1;
}
if sel_end > self.value.len() {
sel_end = self.value.len();
}
let (display_sel_start, display_sel_end) = if self.is_password && !self.password_visible {
match self.password_mask_mode {
PasswordMaskMode::All => {
let char_start = self.value[..sel_start].chars().count();
let char_end = self.value[..sel_end].chars().count();
let bullet_len = "•".len();
(char_start * bullet_len, char_end * bullet_len)
}
PasswordMaskMode::Partial { prefix_len, suffix_len } => {
let chars: Vec<char> = self.value.chars().collect();
let len = chars.len();
if len <= prefix_len + suffix_len {
let char_start = self.value[..sel_start].chars().count();
let char_end = self.value[..sel_end].chars().count();
let bullet_len = "•".len();
(char_start * bullet_len, char_end * bullet_len)
} else {
let mut display_start = 0;
let mut display_end = 0;
let sel_start_chars = self.value[..sel_start.min(self.value.len())].chars().count();
let sel_end_chars = self.value[..sel_end.min(self.value.len())].chars().count();
for (char_idx, ch) in chars.iter().enumerate() {
if char_idx < sel_start_chars {
if char_idx < prefix_len {
display_start += ch.len_utf8();
} else if char_idx >= len - suffix_len {
display_start += ch.len_utf8();
} else {
display_start += "•".len();
}
}
if char_idx < sel_end_chars {
if char_idx < prefix_len {
display_end += ch.len_utf8();
} else if char_idx >= len - suffix_len {
display_end += ch.len_utf8();
} else {
display_end += "•".len();
}
}
}
(display_start, display_end)
}
}
}
} else {
(sel_start, sel_end)
};
let mut runs = Vec::new();
if display_sel_start > 0 {
runs.push(TextRun {
len: display_sel_start,
font: font.clone(),
color: rgb(0x333333).into(),
background_color: None,
underline: None,
strikethrough: None,
});
}
if display_sel_end > display_sel_start {
runs.push(TextRun {
len: display_sel_end - display_sel_start,
font: font.clone(),
color: rgb(0xFFFFFF).into(),
background_color: Some(rgb(0x4A90E2).into()),
underline: None,
strikethrough: None,
});
}
if display_sel_end < display_text.len() {
runs.push(TextRun {
len: display_text.len() - display_sel_end,
font: font.clone(),
color: rgb(0x333333).into(),
background_color: None,
underline: None,
strikethrough: None,
});
}
(display_text, runs)
}
fn delete_selection(&mut self) -> usize {
if let (Some(start), Some(end)) = (self.selection_start, self.selection_end) {
let (mut sel_start, mut sel_end) = if start <= end {
(start, end)
} else {
(end, start)
};
sel_start = sel_start.min(self.value.len());
sel_end = sel_end.min(self.value.len());
while sel_start > 0 && !self.value.is_char_boundary(sel_start) {
sel_start -= 1;
}
while sel_end > 0 && sel_end < self.value.len() && !self.value.is_char_boundary(sel_end) {
sel_end += 1;
}
if sel_start < sel_end && sel_end <= self.value.len() {
let before = self.value[..sel_start].to_string();
let after = self.value[sel_end..].to_string();
self.value = format!("{}{}", before, after);
}
self.clear_selection();
sel_start
} else {
self.cursor_position
}
}
fn handle_backspace(&mut self, cx: &mut Context<Self>) {
if self.disabled {
return;
}
if self.marked_range.is_some() {
self.marked_range = None;
cx.notify();
return;
}
if self.has_selection() {
self.cursor_position = self.delete_selection();
self.pause_blinking(cx);
cx.emit(TextInputEvent::Change(self.value.clone()));
cx.notify();
return;
}
if self.cursor_position == 0 {
return;
}
if self.cursor_position > self.value.len() {
self.cursor_position = self.value.len();
}
if !self.value.is_char_boundary(self.cursor_position) {
while self.cursor_position > 0 && !self.value.is_char_boundary(self.cursor_position) {
self.cursor_position -= 1;
}
}
let mut prev_pos = self.cursor_position;
while prev_pos > 0 {
prev_pos -= 1;
if self.value.is_char_boundary(prev_pos) {
break;
}
}
let mut new_value = String::new();
new_value.push_str(&self.value[..prev_pos]);
if self.cursor_position <= self.value.len() {
new_value.push_str(&self.value[self.cursor_position..]);
}
self.value = new_value.clone();
self.cursor_position = prev_pos;
self.pause_blinking(cx);
cx.emit(TextInputEvent::Change(new_value));
cx.notify();
}
fn handle_delete(&mut self, cx: &mut Context<Self>) {
if self.disabled {
return;
}
if self.marked_range.is_some() {
self.marked_range = None;
cx.notify();
return;
}
if self.has_selection() {
self.cursor_position = self.delete_selection();
self.pause_blinking(cx);
cx.emit(TextInputEvent::Change(self.value.clone()));
cx.notify();
return;
}
if self.cursor_position > self.value.len() {
self.cursor_position = self.value.len();
}
if self.cursor_position >= self.value.len() {
return;
}
if !self.value.is_char_boundary(self.cursor_position) {
while self.cursor_position > 0 && !self.value.is_char_boundary(self.cursor_position) {
self.cursor_position -= 1;
}
}
let mut next_pos = self.cursor_position + 1;
while next_pos < self.value.len() && !self.value.is_char_boundary(next_pos) {
next_pos += 1;
}
let mut new_value = String::new();
if self.cursor_position <= self.value.len() {
new_value.push_str(&self.value[..self.cursor_position]);
}
if next_pos <= self.value.len() {
new_value.push_str(&self.value[next_pos..]);
}
self.value = new_value.clone();
self.pause_blinking(cx);
cx.emit(TextInputEvent::Change(new_value));
cx.notify();
}
fn extend_selection_to(&mut self, pos: usize) {
if !self.has_selection() {
self.selection_start = Some(pos);
self.selection_end = Some(pos);
} else {
if let (Some(start), Some(end)) = (self.selection_start, self.selection_end) {
let (sel_start, sel_end) = if start <= end {
(start, end)
} else {
(end, start)
};
if (pos as i32 - sel_start as i32).abs() < (pos as i32 - sel_end as i32).abs() {
self.selection_start = Some(pos);
self.selection_end = Some(sel_end);
} else {
self.selection_start = Some(sel_start);
self.selection_end = Some(pos);
}
}
}
}
fn move_cursor_left(&mut self, extend_selection: bool, cx: &mut Context<Self>) {
if self.cursor_position > 0 {
let old_pos = self.cursor_position;
let mut new_pos = self.cursor_position;
while new_pos > 0 {
new_pos -= 1;
if self.value.is_char_boundary(new_pos) {
break;
}
}
self.cursor_position = new_pos;
if extend_selection {
if !self.has_selection() {
self.selection_start = Some(old_pos);
self.selection_end = Some(self.cursor_position);
} else {
self.extend_selection_to(self.cursor_position);
}
} else {
self.clear_selection();
}
self.pause_blinking(cx);
cx.notify();
}
}
fn move_cursor_right(&mut self, extend_selection: bool, cx: &mut Context<Self>) {
if self.cursor_position < self.value.len() {
let old_pos = self.cursor_position;
let mut new_pos = self.cursor_position + 1;
while new_pos < self.value.len() && !self.value.is_char_boundary(new_pos) {
new_pos += 1;
}
self.cursor_position = new_pos;
if extend_selection {
if !self.has_selection() {
self.selection_start = Some(old_pos);
self.selection_end = Some(self.cursor_position);
} else {
self.extend_selection_to(self.cursor_position);
}
} else {
self.clear_selection();
}
self.pause_blinking(cx);
cx.notify();
}
}
fn move_cursor_home(&mut self, extend_selection: bool, cx: &mut Context<Self>) {
let old_pos = self.cursor_position;
self.cursor_position = 0;
if extend_selection {
if !self.has_selection() {
self.selection_start = Some(old_pos);
self.selection_end = Some(0);
} else {
self.extend_selection_to(0);
}
} else {
self.clear_selection();
}
self.pause_blinking(cx);
cx.notify();
}
fn move_cursor_end(&mut self, extend_selection: bool, cx: &mut Context<Self>) {
let old_pos = self.cursor_position;
self.cursor_position = self.value.len();
if extend_selection {
if !self.has_selection() {
self.selection_start = Some(old_pos);
self.selection_end = Some(self.value.len());
} else {
self.extend_selection_to(self.value.len());
}
} else {
self.clear_selection();
}
self.pause_blinking(cx);
cx.notify();
}
fn handle_submit(&mut self, cx: &mut Context<Self>) {
if self.disabled {
return;
}
cx.emit(TextInputEvent::Submit(self.value.clone()));
}
fn build_password_display_text(&self) -> String {
if self.value.is_empty() {
return String::new();
}
match self.password_mask_mode {
PasswordMaskMode::All => {
"•".repeat(self.value.len())
}
PasswordMaskMode::Partial { prefix_len, suffix_len } => {
let chars: Vec<char> = self.value.chars().collect();
let len = chars.len();
if len <= prefix_len + suffix_len {
return "•".repeat(len);
}
let prefix: String = chars[..prefix_len.min(len)].iter().collect();
let suffix: String = chars[len.saturating_sub(suffix_len)..].iter().collect();
let mask_len = len - prefix_len - suffix_len;
format!("{}{}{}", prefix, "•".repeat(mask_len), suffix)
}
}
}
fn render_display_text(&self) -> String {
if self.is_password && !self.password_visible && !self.value.is_empty() {
self.build_password_display_text()
} else {
self.value.clone()
}
}
fn range_to_utf16(&self, range: &std::ops::Range<usize>) -> std::ops::Range<usize> {
let start_idx = range.start.min(self.value.len());
let end_idx = range.end.min(self.value.len());
let start = self.value[..start_idx].encode_utf16().count();
let end = self.value[..end_idx].encode_utf16().count();
start..end
}
fn range_from_utf16(&self, range_utf16: &std::ops::Range<usize>) -> std::ops::Range<usize> {
let mut utf16_count = 0;
let mut start = None;
let mut end = None;
for (byte_idx, ch) in self.value.char_indices() {
if start.is_none() && utf16_count >= range_utf16.start {
start = Some(byte_idx);
}
utf16_count += ch.len_utf16();
if end.is_none() && utf16_count >= range_utf16.end {
end = Some(byte_idx + ch.len_utf8());
break;
}
}
let start = start.unwrap_or(self.value.len());
let end = end.unwrap_or(self.value.len());
start..end
}
fn selected_range(&self) -> std::ops::Range<usize> {
if let (Some(start), Some(end)) = (self.selection_start, self.selection_end) {
if start <= end {
start..end
} else {
end..start
}
} else {
self.cursor_position..self.cursor_position
}
}
}
impl EventEmitter<TextInputEvent> for TextInput {}
impl Focusable for TextInput {
fn focus_handle(&self, _cx: &App) -> FocusHandle {
self.focus_handle.clone()
}
}
impl Render for TextInput {
fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
let is_focused = self.focus_handle.is_focused(window);
if is_focused && self._blink_task.is_none() {
let epoch = self.next_blink_epoch();
self.start_blinking(epoch, cx);
}
let display_text = self.render_display_text();
let show_placeholder = self.value.is_empty();
let disabled = self.disabled;
let placeholder = self.placeholder.clone();
let layout_container: Arc<Mutex<Option<TextInputLayout>>> = Arc::new(Mutex::new(None));
div()
.id("text-input")
.track_focus(&self.focus_handle)
.on_key_down(cx.listener(|this, event: &KeyDownEvent, _, cx| {
if this.disabled {
return;
}
let modifiers = &event.keystroke.modifiers;
let is_cmd_or_ctrl = if cfg!(target_os = "macos") {
modifiers.platform
} else {
modifiers.control
};
if is_cmd_or_ctrl && event.keystroke.key.as_str().eq_ignore_ascii_case("a") {
this.select_all(cx);
return;
}
let shift_pressed = modifiers.shift;
match event.keystroke.key.as_str() {
"backspace" => {
this.handle_backspace(cx);
}
"delete" => {
this.handle_delete(cx);
}
"left" => {
this.move_cursor_left(shift_pressed, cx);
}
"right" => {
this.move_cursor_right(shift_pressed, cx);
}
"home" => {
this.move_cursor_home(shift_pressed, cx);
}
"end" => {
this.move_cursor_end(shift_pressed, cx);
}
"enter" => {
this.handle_submit(cx);
}
_ => {
}
}
}))
.on_mouse_down(MouseButton::Left, {
let layout_container = layout_container.clone();
cx.listener(move |this, event: &MouseDownEvent, window, cx| {
cx.emit(TextInputEvent::Focus);
cx.focus_self(window);
if let Ok(layout) = layout_container.lock() {
this.last_layout = layout.clone();
}
this.is_dragging = true;
let index = this.index_for_mouse_position(event.position);
if event.modifiers.shift {
if !this.has_selection() {
this.selection_start = Some(this.cursor_position);
this.selection_end = Some(index);
} else {
this.selection_end = Some(index);
}
} else {
this.clear_selection();
}
this.cursor_position = index;
this.pause_blinking(cx);
cx.notify();
})
})
.on_mouse_up(MouseButton::Left, cx.listener(|this, _event: &MouseUpEvent, _window, cx| {
this.is_dragging = false;
cx.notify();
}))
.on_mouse_move(cx.listener(|this, event: &MouseMoveEvent, _window, cx| {
if this.is_dragging {
let index = this.index_for_mouse_position(event.position);
if !this.has_selection() {
this.selection_start = Some(this.cursor_position);
this.selection_end = Some(index);
} else {
this.selection_end = Some(index);
}
this.cursor_position = index;
cx.notify();
}
}))
.flex()
.items_center()
.w_full()
.h(px(36.))
.px_3()
.bg(self.custom_bg_color.unwrap_or(if disabled {
rgb(0xF5F5F5)
} else {
rgb(0xFFFFFF)
}))
.when(self.show_border, |this| {
this.border_1()
.border_color(self.custom_border_color.unwrap_or(if is_focused {
rgb(0x696FC7)
} else {
rgb(0xE0E0E0)
}))
})
.rounded(px(6.))
.when(!disabled, |this| {
this.cursor(CursorStyle::IBeam)
})
.child(TextInputElement {
entity: cx.entity().clone(),
})
.child(
div()
.flex()
.items_center()
.flex_1()
.min_w(px(0.)) .text_sm()
.when(show_placeholder, |this| {
if is_focused && !disabled && !self.has_selection() && self.cursor_visible {
this.text_color(rgb(0x999999))
.relative()
.child(placeholder)
.child(
div()
.absolute()
.left(px(0.))
.top(px(2.)) .w(px(2.))
.h(px(18.))
.bg(rgb(0x000000)) )
} else {
this.text_color(rgb(0x999999))
.child(placeholder)
}
})
.when(!show_placeholder && !is_focused, |this| {
this.text_color(if disabled {
rgb(0x999999)
} else {
rgb(0x333333)
})
.child(display_text.clone())
})
.when(!show_placeholder && is_focused && !disabled, |this| {
let cursor_pos = self.cursor_position;
let font = gpui::Font {
family: ".SystemUIFont".into(),
features: Default::default(),
weight: Default::default(),
style: Default::default(),
fallbacks: None,
};
let font_size = px(14.);
let (display_text, text_runs) = self.build_text_runs(font.clone(), font_size);
let display_text_for_cursor = display_text.clone(); let has_selection = self.has_selection();
let cursor_visible = self.cursor_visible;
let is_password = self.is_password;
let password_visible = self.password_visible;
let display_cursor_pos = if is_password && !password_visible {
match self.password_mask_mode {
PasswordMaskMode::All => {
let char_count = self.value[..cursor_pos.min(self.value.len())].chars().count();
char_count * "•".len()
}
PasswordMaskMode::Partial { prefix_len, suffix_len } => {
let chars: Vec<char> = self.value.chars().collect();
let len = chars.len();
if len <= prefix_len + suffix_len {
let char_count = self.value[..cursor_pos.min(self.value.len())].chars().count();
char_count * "•".len()
} else {
let mut display_pos = 0;
for (i, ch) in chars.iter().enumerate() {
let value_byte_pos = self.value[..i].len() + ch.len_utf8();
if value_byte_pos > cursor_pos {
break;
}
if i < prefix_len {
display_pos += ch.len_utf8();
} else if i >= len - suffix_len {
display_pos += ch.len_utf8();
} else {
display_pos += "•".len();
}
}
display_pos
}
}
}
} else {
cursor_pos
};
let layout_container_for_canvas = layout_container.clone();
this.child(
div()
.flex()
.items_center()
.h_full()
.w_full() .relative() .child(
canvas(
move |bounds, _, _cx| {
gpui::size(bounds.size.width, px(18.))
},
move |bounds, _, window, _cx| {
let line_height = px(18.);
if !display_text.is_empty() {
let shaped_line = window.text_system().shape_line(
display_text.clone().into(),
font_size,
&text_runs,
None,
);
if let Ok(mut layout) = layout_container_for_canvas.lock() {
*layout = Some(TextInputLayout {
bounds,
shaped_line: shaped_line.clone(),
});
}
let origin = bounds.origin;
shaped_line.paint_background(origin, line_height, window, _cx).ok();
shaped_line.paint(origin, line_height, window, _cx).ok();
}
},
)
.w_full()
.h(px(18.))
)
.child(
canvas(
move |_bounds, _, _cx| {
gpui::size(px(0.), px(0.)) },
move |bounds, _, window, _cx| {
if !has_selection && cursor_visible {
let cursor_x = if display_cursor_pos == 0 || display_text_for_cursor.is_empty() {
px(0.)
} else {
let text_before = if display_cursor_pos <= display_text_for_cursor.len() {
display_text_for_cursor[..display_cursor_pos].to_string()
} else {
display_text_for_cursor.clone()
};
if text_before.is_empty() {
px(0.)
} else {
let temp_runs = vec![TextRun {
len: text_before.len(),
font: font.clone(),
color: rgb(0x333333).into(),
background_color: None,
underline: None,
strikethrough: None,
}];
let temp_line = window.text_system().shape_line(
text_before.into(),
font_size,
&temp_runs,
None,
);
temp_line.width
}
};
let cursor_bounds = gpui::Bounds {
origin: bounds.origin + gpui::point(cursor_x, px(1.)),
size: gpui::size(px(2.), px(18.)),
};
window.paint_quad(gpui::fill(cursor_bounds, rgb(0x000000)));
}
},
)
.absolute()
.top(px(0.))
.left(px(0.))
.w(px(0.))
.h(px(18.))
)
)
})
)
}
}
impl EntityInputHandler for TextInput {
fn text_for_range(
&mut self,
range_utf16: std::ops::Range<usize>,
actual_range: &mut Option<std::ops::Range<usize>>,
_window: &mut Window,
_cx: &mut Context<Self>,
) -> Option<String> {
let range = self.range_from_utf16(&range_utf16);
actual_range.replace(self.range_to_utf16(&range));
Some(self.value[range].to_string())
}
fn selected_text_range(
&mut self,
_ignore_disabled_input: bool,
_window: &mut Window,
_cx: &mut Context<Self>,
) -> Option<UTF16Selection> {
let range = self.selected_range();
Some(UTF16Selection {
range: self.range_to_utf16(&range),
reversed: false, })
}
fn marked_text_range(
&self,
_window: &mut Window,
_cx: &mut Context<Self>,
) -> Option<std::ops::Range<usize>> {
self.marked_range
.as_ref()
.map(|range| self.range_to_utf16(range))
}
fn unmark_text(&mut self, _window: &mut Window, _cx: &mut Context<Self>) {
self.marked_range = None;
}
fn replace_text_in_range(
&mut self,
range_utf16: Option<std::ops::Range<usize>>,
new_text: &str,
_window: &mut Window,
cx: &mut Context<Self>,
) {
let mut range = range_utf16
.as_ref()
.map(|range_utf16| self.range_from_utf16(range_utf16))
.or(self.marked_range.clone())
.unwrap_or_else(|| self.selected_range());
range.start = range.start.min(self.value.len());
range.end = range.end.min(self.value.len());
while range.start > 0 && !self.value.is_char_boundary(range.start) {
range.start -= 1;
}
while range.end > 0 && range.end < self.value.len() && !self.value.is_char_boundary(range.end) {
range.end += 1;
}
if range.end > self.value.len() {
range.end = self.value.len();
}
let mut new_value = String::new();
new_value.push_str(&self.value[..range.start]);
new_value.push_str(new_text);
new_value.push_str(&self.value[range.end..]);
self.value = new_value;
self.cursor_position = range.start + new_text.len();
self.clear_selection();
self.marked_range = None;
self.pause_blinking(cx);
cx.emit(TextInputEvent::Change(self.value.clone()));
cx.notify();
}
fn replace_and_mark_text_in_range(
&mut self,
range_utf16: Option<std::ops::Range<usize>>,
new_text: &str,
new_selected_range_utf16: Option<std::ops::Range<usize>>,
_window: &mut Window,
cx: &mut Context<Self>,
) {
let mut range = range_utf16
.as_ref()
.map(|range_utf16| self.range_from_utf16(range_utf16))
.or(self.marked_range.clone())
.unwrap_or_else(|| self.selected_range());
range.start = range.start.min(self.value.len());
range.end = range.end.min(self.value.len());
while range.start > 0 && !self.value.is_char_boundary(range.start) {
range.start -= 1;
}
while range.end > 0 && range.end < self.value.len() && !self.value.is_char_boundary(range.end) {
range.end += 1;
}
if range.end > self.value.len() {
range.end = self.value.len();
}
let mut new_value = String::new();
new_value.push_str(&self.value[..range.start]);
new_value.push_str(new_text);
new_value.push_str(&self.value[range.end..]);
self.value = new_value;
if !new_text.is_empty() {
self.marked_range = Some(range.start..range.start + new_text.len());
} else {
self.marked_range = None;
}
if let Some(new_range_utf16) = new_selected_range_utf16 {
let new_range = self.range_from_utf16(&new_range_utf16);
self.cursor_position = range.start + new_range.end;
} else {
self.cursor_position = range.start + new_text.len();
}
self.clear_selection();
self.pause_blinking(cx);
cx.notify();
}
fn bounds_for_range(
&mut self,
_range_utf16: std::ops::Range<usize>,
bounds: Bounds<Pixels>,
_window: &mut Window,
_cx: &mut Context<Self>,
) -> Option<Bounds<Pixels>> {
Some(bounds)
}
fn character_index_for_point(
&mut self,
point: Point<Pixels>,
_window: &mut Window,
_cx: &mut Context<Self>,
) -> Option<usize> {
let byte_index = self.index_for_mouse_position(point);
Some(self.value[..byte_index].encode_utf16().count())
}
}