Add cursor_layout
This commit is contained in:
parent
ae5e4f6149
commit
b70f5d6dc7
9 changed files with 339 additions and 239 deletions
|
|
@ -1,5 +1,5 @@
|
|||
use gpui::{
|
||||
div, prelude::FluentBuilder as _, AbsoluteLength, DefiniteLength, Div, Half, Hsla, IntoElement,
|
||||
div, prelude::FluentBuilder as _, AbsoluteLength, DefiniteLength, Div, Hsla, IntoElement,
|
||||
ParentElement, RenderOnce, SharedString, Styled, WindowContext,
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
use gpui::{
|
||||
div, ClickEvent, IntoElement, ParentElement as _, Render, RenderOnce, Styled as _, ViewContext,
|
||||
div, ClickEvent, IntoElement, ParentElement as _, Render, Styled as _, ViewContext,
|
||||
WindowContext,
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
use gpui::{
|
||||
div, ClickEvent, IntoElement, ParentElement as _, Render, Styled as _, ViewContext,
|
||||
WindowContext,
|
||||
VisualContext as _, WindowContext,
|
||||
};
|
||||
|
||||
use crate::text_field::TextField;
|
||||
|
|
@ -24,8 +24,8 @@ impl Render for InputStory {
|
|||
.flex_col()
|
||||
.justify_start()
|
||||
.gap_3()
|
||||
.child(TextField::new(cx, "Enter text here...", false))
|
||||
.child(TextField::new(cx, "Enter text here...", false)),
|
||||
.child(cx.new_view(|cx| TextField::new("Enter text here...", false, cx)))
|
||||
.child(cx.new_view(|cx| TextField::new("Enter text here...", false, cx))),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +0,0 @@
|
|||
mod blink_manager;
|
||||
mod cursor_layout;
|
||||
mod text_field;
|
||||
|
||||
pub use text_field::*;
|
||||
|
|
@ -11,7 +11,7 @@ pub struct BlinkManager {
|
|||
}
|
||||
|
||||
impl BlinkManager {
|
||||
pub fn new(blink_interval: Duration) -> Self {
|
||||
pub fn new(blink_interval: Duration, _cx: &mut ModelContext<Self>) -> Self {
|
||||
Self {
|
||||
blink_interval,
|
||||
blink_epoch: 0,
|
||||
|
|
@ -36,4 +36,12 @@ impl BlinkManager {
|
|||
pub fn disable(&mut self, _cx: &mut ModelContext<Self>) {
|
||||
self.enabled = false;
|
||||
}
|
||||
|
||||
pub fn enable(&mut self, _cx: &mut ModelContext<Self>) {
|
||||
self.enabled = true;
|
||||
}
|
||||
|
||||
pub fn visible(&self) -> bool {
|
||||
self.visible
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
use gpui::{outline, px, Bounds, Hsla, Pixels, ShapedLine, Size, ViewContext};
|
||||
use gpui::{outline, px, Bounds, Hsla, Pixels, ShapedLine, Size, ViewContext, WindowContext};
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct CursorLayout {
|
||||
origin: gpui::Point<Pixels>,
|
||||
#[allow(unused)]
|
||||
|
|
@ -36,7 +37,7 @@ impl CursorLayout {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn paint(&mut self, origin: gpui::Point<Pixels>, cx: &mut ViewContext<Self>) {
|
||||
pub fn paint(&mut self, origin: gpui::Point<Pixels>, cx: &mut WindowContext) {
|
||||
let bounds = self.bounds(origin);
|
||||
|
||||
let cursor = outline(bounds, self.color);
|
||||
|
|
|
|||
|
|
@ -1,30 +1,41 @@
|
|||
use std::{ops::Range, time::Duration};
|
||||
|
||||
use gpui::{
|
||||
div, ClipboardItem, Context, EventEmitter, FocusHandle, HighlightStyle, InteractiveElement,
|
||||
InteractiveText, IntoElement, KeyDownEvent, Model, ParentElement, Render, RenderOnce, Styled,
|
||||
StyledText, TextStyle, View, ViewContext, VisualContext, WindowContext,
|
||||
};
|
||||
mod blink_manager;
|
||||
mod cursor_layout;
|
||||
mod text_view;
|
||||
|
||||
use crate::{disableable::Disableable, theme::Theme};
|
||||
use blink_manager::BlinkManager;
|
||||
use gpui::{
|
||||
div, px, relative, ClipboardItem, Context, Element, EventEmitter, FocusHandle, HighlightStyle,
|
||||
InteractiveElement, InteractiveText, IntoElement, KeyDownEvent, Model, ParentElement, Pixels,
|
||||
Render, Style, Styled, StyledText, TextStyle, View, ViewContext, VisualContext, WindowContext,
|
||||
};
|
||||
use std::{ops::Range, time::Duration};
|
||||
use text_view::TextView;
|
||||
|
||||
use super::{blink_manager::BlinkManager, cursor_layout::CursorLayout};
|
||||
const CURSOR_BLINK_INTERVAL: Duration = Duration::from_millis(500);
|
||||
|
||||
#[derive(IntoElement, Clone)]
|
||||
#[derive(Clone)]
|
||||
pub struct TextField {
|
||||
focus_handle: FocusHandle,
|
||||
disable: bool,
|
||||
blink_manager: Model<BlinkManager>,
|
||||
pub view: View<TextView>,
|
||||
}
|
||||
|
||||
impl TextField {
|
||||
pub fn new(cx: &mut WindowContext, placeholder: &str, disable: bool) -> Self {
|
||||
pub fn new(placeholder: &str, disable: bool, cx: &mut ViewContext<Self>) -> Self {
|
||||
let focus_handle = cx.focus_handle();
|
||||
let view = TextView::init(cx, &focus_handle, placeholder, disable);
|
||||
|
||||
let blink_manager = cx.new_model(|cx| BlinkManager::new(CURSOR_BLINK_INTERVAL, cx));
|
||||
|
||||
cx.on_focus(&focus_handle, Self::handle_focus).detach();
|
||||
cx.on_blur(&focus_handle, Self::handle_blur).detach();
|
||||
|
||||
Self {
|
||||
focus_handle,
|
||||
view,
|
||||
blink_manager,
|
||||
disable,
|
||||
}
|
||||
}
|
||||
|
|
@ -32,6 +43,22 @@ impl TextField {
|
|||
pub fn focus(&self, cx: &mut WindowContext) {
|
||||
cx.focus(&self.focus_handle);
|
||||
}
|
||||
|
||||
fn handle_focus(&mut self, cx: &mut ViewContext<Self>) {
|
||||
cx.emit(TextEvent::Focus);
|
||||
self.blink_manager.update(cx, BlinkManager::enable);
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
fn handle_blur(&mut self, cx: &mut ViewContext<Self>) {
|
||||
cx.emit(TextEvent::Blur);
|
||||
self.blink_manager.update(cx, BlinkManager::disable);
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
pub fn show_cursor(&self, cx: &mut WindowContext) -> bool {
|
||||
self.blink_manager.read(cx).visible() && self.focus_handle.is_focused(cx)
|
||||
}
|
||||
}
|
||||
|
||||
impl Disableable for TextField {
|
||||
|
|
@ -41,12 +68,13 @@ impl Disableable for TextField {
|
|||
}
|
||||
}
|
||||
|
||||
impl RenderOnce for TextField {
|
||||
fn render(self, cx: &mut WindowContext) -> impl IntoElement {
|
||||
impl Render for TextField {
|
||||
fn render<'a>(&mut self, cx: &mut ViewContext<'a, Self>) -> impl IntoElement {
|
||||
cx.focus(&self.focus_handle);
|
||||
let theme = cx.global::<Theme>();
|
||||
|
||||
let clone = self.view.clone();
|
||||
let text_view = self.view.clone();
|
||||
let text_view1 = text_view.clone();
|
||||
|
||||
div()
|
||||
.border_color(if self.focus_handle.is_focused(cx) {
|
||||
|
|
@ -57,7 +85,7 @@ impl RenderOnce for TextField {
|
|||
.border_1()
|
||||
.track_focus(&self.focus_handle)
|
||||
.on_key_down(move |ev, cx| {
|
||||
self.view.update(cx, |editor, cx| {
|
||||
text_view1.update(cx, |editor, cx| {
|
||||
let prev = editor.text.clone();
|
||||
cx.emit(TextEvent::KeyDown(ev.clone()));
|
||||
let keystroke = &ev.keystroke.key;
|
||||
|
|
@ -181,213 +209,15 @@ impl RenderOnce for TextField {
|
|||
} else {
|
||||
theme.base
|
||||
})
|
||||
.child(clone)
|
||||
.child(text_view)
|
||||
}
|
||||
}
|
||||
|
||||
pub enum TextEvent {
|
||||
Input { text: String },
|
||||
Blur,
|
||||
Focus,
|
||||
KeyDown(KeyDownEvent),
|
||||
}
|
||||
|
||||
impl EventEmitter<TextEvent> for TextView {}
|
||||
|
||||
pub struct TextView {
|
||||
pub text: String,
|
||||
pub placeholder: String,
|
||||
pub word_click: (usize, u16),
|
||||
pub selection: Range<usize>,
|
||||
pub disable: bool,
|
||||
pub blink_manager: Model<BlinkManager>,
|
||||
pub cursor: CursorLayout,
|
||||
}
|
||||
|
||||
const CURSOR_BLINK_INTERVAL: Duration = Duration::from_millis(500);
|
||||
|
||||
impl TextView {
|
||||
pub fn init(
|
||||
cx: &mut WindowContext,
|
||||
focus_handle: &FocusHandle,
|
||||
placeholder: &str,
|
||||
disable: bool,
|
||||
) -> View<Self> {
|
||||
let blink_manager = cx.new_model(|_cx| BlinkManager::new(CURSOR_BLINK_INTERVAL));
|
||||
|
||||
let cursor = CursorLayout::new(
|
||||
gpui::Point::new(gpui::px(0.0), gpui::px(0.0)),
|
||||
gpui::px(2.0),
|
||||
gpui::px(20.0),
|
||||
gpui::Hsla {
|
||||
h: 0.0,
|
||||
s: 0.0,
|
||||
l: 0.0,
|
||||
a: 1.0,
|
||||
},
|
||||
None,
|
||||
);
|
||||
|
||||
let m = Self {
|
||||
text: String::new(),
|
||||
placeholder: placeholder.to_string(),
|
||||
word_click: (0, 0),
|
||||
selection: 0..0,
|
||||
blink_manager,
|
||||
cursor,
|
||||
disable,
|
||||
};
|
||||
|
||||
let view = cx.new_view(|cx| {
|
||||
cx.on_blur(
|
||||
focus_handle,
|
||||
|view: &mut TextView, cx: &mut ViewContext<'_, TextView>| {
|
||||
view.blink_manager.update(cx, BlinkManager::disable);
|
||||
cx.emit(TextEvent::Blur);
|
||||
},
|
||||
)
|
||||
.detach();
|
||||
|
||||
cx.on_focus(focus_handle, |view, cx| {
|
||||
view.blink_manager.update(cx, |bm, cx| {
|
||||
bm.blink_cursor(0, cx);
|
||||
});
|
||||
})
|
||||
.detach();
|
||||
m
|
||||
});
|
||||
|
||||
cx.subscribe(
|
||||
&view,
|
||||
move |subscriber, emitter: &TextEvent, cx| match emitter {
|
||||
TextEvent::Input { text: _ } => {
|
||||
subscriber.update(cx, |editor, cx| {
|
||||
editor.word_click = (0, 0);
|
||||
});
|
||||
}
|
||||
TextEvent::Blur => {
|
||||
subscriber.update(cx, |editor, cx| {
|
||||
editor.blink_manager.update(cx, BlinkManager::disable);
|
||||
editor.word_click = (0, 0);
|
||||
});
|
||||
}
|
||||
_ => {}
|
||||
},
|
||||
)
|
||||
.detach();
|
||||
|
||||
view
|
||||
}
|
||||
|
||||
pub fn select_all(&mut self, cx: &mut ViewContext<Self>) {
|
||||
self.selection = 0..self.text.len();
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
pub fn word_ranges(&self) -> Vec<Range<usize>> {
|
||||
let mut words = Vec::new();
|
||||
let mut last_was_boundary = true;
|
||||
let mut word_start = 0;
|
||||
let s = self.text.clone();
|
||||
|
||||
for (i, c) in s.char_indices() {
|
||||
if c.is_alphanumeric() || c == '_' {
|
||||
if last_was_boundary {
|
||||
word_start = i;
|
||||
}
|
||||
last_was_boundary = false;
|
||||
} else {
|
||||
if !last_was_boundary {
|
||||
words.push(word_start..i);
|
||||
}
|
||||
last_was_boundary = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Check if the last characters form a word and push it if so
|
||||
if !last_was_boundary {
|
||||
words.push(word_start..s.len());
|
||||
}
|
||||
|
||||
words
|
||||
}
|
||||
|
||||
pub fn char_range_to_text_range(&self, text: &str) -> Range<usize> {
|
||||
let start = text
|
||||
.chars()
|
||||
.take(self.selection.start)
|
||||
.collect::<String>()
|
||||
.len();
|
||||
let end = text
|
||||
.chars()
|
||||
.take(self.selection.end)
|
||||
.collect::<String>()
|
||||
.len();
|
||||
start..end
|
||||
}
|
||||
|
||||
pub fn set_text(&mut self, text: impl ToString, cx: &mut ViewContext<Self>) {
|
||||
self.text = text.to_string();
|
||||
self.selection = self.text.len()..self.text.len();
|
||||
cx.notify();
|
||||
cx.emit(TextEvent::Input {
|
||||
text: self.text.clone(),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
impl Render for TextView {
|
||||
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
|
||||
let theme = cx.global::<Theme>();
|
||||
let mut text = self.text.clone();
|
||||
|
||||
let mut style = TextStyle {
|
||||
color: theme.text,
|
||||
font_family: theme.font_sans.clone(),
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let mut selection_style = HighlightStyle::default();
|
||||
let mut color = theme.lavender;
|
||||
color.fade_out(0.8);
|
||||
selection_style.background_color = Some(color);
|
||||
|
||||
let highlights = vec![(self.char_range_to_text_range(&text), selection_style)];
|
||||
|
||||
let styled_text: StyledText = if text.is_empty() {
|
||||
text = self.placeholder.to_string();
|
||||
style.color = theme.subtext0;
|
||||
StyledText::new(text).with_highlights(&style, highlights)
|
||||
} else {
|
||||
StyledText::new(text).with_highlights(&style, highlights)
|
||||
};
|
||||
|
||||
let view = cx.view().clone();
|
||||
|
||||
InteractiveText::new("text", styled_text).on_click(self.word_ranges(), move |ev, cx| {
|
||||
view.update(cx, |text_view, cx| {
|
||||
let (index, mut count) = text_view.word_click;
|
||||
if index == ev {
|
||||
count += 1;
|
||||
} else {
|
||||
count = 1;
|
||||
}
|
||||
match count {
|
||||
2 => {
|
||||
let word_ranges = text_view.word_ranges();
|
||||
text_view.selection = word_ranges.get(ev).unwrap().clone();
|
||||
}
|
||||
3 => {
|
||||
// Should select the line
|
||||
}
|
||||
4 => {
|
||||
count = 0;
|
||||
text_view.selection = 0..text_view.text.len();
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
text_view.word_click = (ev, count);
|
||||
cx.notify();
|
||||
});
|
||||
})
|
||||
}
|
||||
}
|
||||
impl EventEmitter<TextEvent> for TextField {}
|
||||
270
crates/ui/src/text_field/text_view.rs
Normal file
270
crates/ui/src/text_field/text_view.rs
Normal file
|
|
@ -0,0 +1,270 @@
|
|||
use std::{ops::Range, time::Duration};
|
||||
|
||||
use gpui::{
|
||||
div, px, relative, ClipboardItem, Context, Element, EventEmitter, FocusHandle, HighlightStyle,
|
||||
InteractiveElement, InteractiveText, IntoElement, KeyDownEvent, Model, ParentElement, Pixels,
|
||||
Render, Style, Styled, StyledText, TextStyle, View, ViewContext, VisualContext, WindowContext,
|
||||
};
|
||||
|
||||
use crate::{disableable::Disableable, hls, theme::Theme};
|
||||
|
||||
use super::{
|
||||
blink_manager::BlinkManager, cursor_layout::CursorLayout, TextEvent, CURSOR_BLINK_INTERVAL,
|
||||
};
|
||||
|
||||
pub struct TextView {
|
||||
pub text: String,
|
||||
pub placeholder: String,
|
||||
pub word_click: (usize, u16),
|
||||
pub selection: Range<usize>,
|
||||
pub disable: bool,
|
||||
pub blink_manager: Model<BlinkManager>,
|
||||
pub cursor: CursorLayout,
|
||||
}
|
||||
|
||||
impl EventEmitter<TextEvent> for TextView {}
|
||||
|
||||
impl TextView {
|
||||
pub fn init(
|
||||
cx: &mut WindowContext,
|
||||
focus_handle: &FocusHandle,
|
||||
placeholder: &str,
|
||||
disable: bool,
|
||||
) -> View<Self> {
|
||||
let blink_manager = cx.new_model(|cx| BlinkManager::new(CURSOR_BLINK_INTERVAL, cx));
|
||||
|
||||
let cursor = CursorLayout::new(
|
||||
gpui::Point::new(gpui::px(0.0), gpui::px(0.0)),
|
||||
px(2.0),
|
||||
px(20.0),
|
||||
hls(212., 92., 45.),
|
||||
None,
|
||||
);
|
||||
|
||||
let m = Self {
|
||||
text: String::new(),
|
||||
placeholder: placeholder.to_string(),
|
||||
word_click: (0, 0),
|
||||
selection: 0..0,
|
||||
blink_manager,
|
||||
cursor,
|
||||
disable,
|
||||
};
|
||||
|
||||
let view = cx.new_view(|cx| {
|
||||
cx.on_blur(
|
||||
focus_handle,
|
||||
|view: &mut TextView, cx: &mut ViewContext<'_, TextView>| {
|
||||
view.blink_manager.update(cx, BlinkManager::disable);
|
||||
cx.emit(TextEvent::Blur);
|
||||
},
|
||||
)
|
||||
.detach();
|
||||
|
||||
cx.on_focus(focus_handle, |view, cx| {
|
||||
view.blink_manager.update(cx, |bm, cx| {
|
||||
bm.blink_cursor(0, cx);
|
||||
});
|
||||
})
|
||||
.detach();
|
||||
m
|
||||
});
|
||||
|
||||
cx.subscribe(
|
||||
&view,
|
||||
move |subscriber, emitter: &TextEvent, cx| match emitter {
|
||||
TextEvent::Input { text: _ } => {
|
||||
subscriber.update(cx, |editor, cx| {
|
||||
editor.word_click = (0, 0);
|
||||
});
|
||||
}
|
||||
TextEvent::Blur => {
|
||||
subscriber.update(cx, |editor, cx| {
|
||||
editor.blink_manager.update(cx, BlinkManager::disable);
|
||||
editor.word_click = (0, 0);
|
||||
});
|
||||
}
|
||||
_ => {}
|
||||
},
|
||||
)
|
||||
.detach();
|
||||
|
||||
view
|
||||
}
|
||||
|
||||
pub fn select_all(&mut self, cx: &mut ViewContext<Self>) {
|
||||
self.selection = 0..self.text.len();
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
pub fn word_ranges(&self) -> Vec<Range<usize>> {
|
||||
let mut words = Vec::new();
|
||||
let mut last_was_boundary = true;
|
||||
let mut word_start = 0;
|
||||
let s = self.text.clone();
|
||||
|
||||
for (i, c) in s.char_indices() {
|
||||
if c.is_alphanumeric() || c == '_' {
|
||||
if last_was_boundary {
|
||||
word_start = i;
|
||||
}
|
||||
last_was_boundary = false;
|
||||
} else {
|
||||
if !last_was_boundary {
|
||||
words.push(word_start..i);
|
||||
}
|
||||
last_was_boundary = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Check if the last characters form a word and push it if so
|
||||
if !last_was_boundary {
|
||||
words.push(word_start..s.len());
|
||||
}
|
||||
|
||||
words
|
||||
}
|
||||
|
||||
pub fn char_range_to_text_range(&self, text: &str) -> Range<usize> {
|
||||
let start = text
|
||||
.chars()
|
||||
.take(self.selection.start)
|
||||
.collect::<String>()
|
||||
.len();
|
||||
let end = text
|
||||
.chars()
|
||||
.take(self.selection.end)
|
||||
.collect::<String>()
|
||||
.len();
|
||||
start..end
|
||||
}
|
||||
|
||||
pub fn set_text(&mut self, text: impl ToString, cx: &mut ViewContext<Self>) {
|
||||
self.text = text.to_string();
|
||||
self.selection = self.text.len()..self.text.len();
|
||||
cx.notify();
|
||||
cx.emit(TextEvent::Input {
|
||||
text: self.text.clone(),
|
||||
});
|
||||
}
|
||||
|
||||
fn paint_cursors(&self, layout: &TextLayout, cx: &mut WindowContext) {
|
||||
let mut cursor = self.cursor.clone();
|
||||
dbg!("--------- paint_cursors", &cursor);
|
||||
cursor.paint(layout.content_origin, cx);
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoElement for TextView {
|
||||
type Element = Self;
|
||||
|
||||
fn into_element(self) -> Self::Element {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
pub struct TextLayout {
|
||||
content_origin: gpui::Point<gpui::Pixels>,
|
||||
}
|
||||
|
||||
impl Element for TextView {
|
||||
type RequestLayoutState = ();
|
||||
|
||||
type PrepaintState = TextLayout;
|
||||
|
||||
fn id(&self) -> Option<gpui::ElementId> {
|
||||
None
|
||||
}
|
||||
|
||||
fn request_layout(
|
||||
&mut self,
|
||||
id: Option<&gpui::GlobalElementId>,
|
||||
cx: &mut WindowContext,
|
||||
) -> (gpui::LayoutId, Self::RequestLayoutState) {
|
||||
let mut style = Style::default();
|
||||
style.size.width = relative(1.).into();
|
||||
style.size.height = relative(24.).into();
|
||||
|
||||
(cx.request_layout(style, None), ())
|
||||
}
|
||||
|
||||
fn prepaint(
|
||||
&mut self,
|
||||
id: Option<&gpui::GlobalElementId>,
|
||||
bounds: gpui::Bounds<gpui::Pixels>,
|
||||
request_layout: &mut Self::RequestLayoutState,
|
||||
cx: &mut WindowContext,
|
||||
) -> Self::PrepaintState {
|
||||
TextLayout {
|
||||
content_origin: bounds.origin,
|
||||
}
|
||||
}
|
||||
|
||||
fn paint(
|
||||
&mut self,
|
||||
id: Option<&gpui::GlobalElementId>,
|
||||
bounds: gpui::Bounds<gpui::Pixels>,
|
||||
request_layout: &mut Self::RequestLayoutState,
|
||||
layout: &mut Self::PrepaintState,
|
||||
cx: &mut WindowContext,
|
||||
) {
|
||||
self.paint_cursors(layout, cx);
|
||||
}
|
||||
}
|
||||
|
||||
impl Render for TextView {
|
||||
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
|
||||
let theme = cx.global::<Theme>();
|
||||
let mut text = self.text.clone();
|
||||
|
||||
let mut style = TextStyle {
|
||||
color: theme.text,
|
||||
font_family: theme.font_sans.clone(),
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let mut selection_style = HighlightStyle::default();
|
||||
let mut color = theme.lavender;
|
||||
color.fade_out(0.8);
|
||||
selection_style.background_color = Some(color);
|
||||
|
||||
let highlights = vec![(self.char_range_to_text_range(&text), selection_style)];
|
||||
|
||||
let styled_text: StyledText = if text.is_empty() {
|
||||
text = self.placeholder.to_string();
|
||||
style.color = theme.subtext0;
|
||||
StyledText::new(text).with_highlights(&style, highlights)
|
||||
} else {
|
||||
StyledText::new(text).with_highlights(&style, highlights)
|
||||
};
|
||||
|
||||
let view = cx.view().clone();
|
||||
|
||||
InteractiveText::new("text", styled_text).on_click(self.word_ranges(), move |ev, cx| {
|
||||
view.update(cx, |text_view, cx| {
|
||||
let (index, mut count) = text_view.word_click;
|
||||
if index == ev {
|
||||
count += 1;
|
||||
} else {
|
||||
count = 1;
|
||||
}
|
||||
match count {
|
||||
2 => {
|
||||
let word_ranges = text_view.word_ranges();
|
||||
text_view.selection = word_ranges.get(ev).unwrap().clone();
|
||||
}
|
||||
3 => {
|
||||
// Should select the line
|
||||
}
|
||||
4 => {
|
||||
count = 0;
|
||||
text_view.selection = 0..text_view.text.len();
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
text_view.word_click = (ev, count);
|
||||
cx.notify();
|
||||
});
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
@ -1,13 +1,7 @@
|
|||
use gpui::{prelude::FluentBuilder, *};
|
||||
|
||||
use std::sync::Arc;
|
||||
use ui::{
|
||||
button::{Button, ButtonStyle},
|
||||
disableable::Clickable as _,
|
||||
text_field::TextField,
|
||||
theme::Theme,
|
||||
Color,
|
||||
};
|
||||
use ui::{button::Button, disableable::Clickable as _, text_field::TextField, theme::Theme, Color};
|
||||
use util::ResultExt as _;
|
||||
|
||||
mod app_state;
|
||||
|
|
@ -127,10 +121,12 @@ impl Render for Workspace {
|
|||
.child(ui::story::Stories::view(cx)),
|
||||
)
|
||||
.child({
|
||||
let txt = TextField::new(cx, "Enter text here...", false);
|
||||
txt.view
|
||||
.update(cx, |this, cx| this.set_text("This is default text.", cx));
|
||||
txt
|
||||
cx.new_view(|cx| {
|
||||
let txt = TextField::new("Enter text here...", false, cx);
|
||||
txt.view
|
||||
.update(cx, |this, cx| this.set_text("This is default text.", cx));
|
||||
txt
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue