Fix stories switch.

This commit is contained in:
Jason Lee 2024-06-24 14:27:07 +08:00
parent b70f5d6dc7
commit 11af9900eb
6 changed files with 140 additions and 125 deletions

View file

@ -138,6 +138,7 @@ impl RenderOnce for Button {
})
.when(!self.disabled, |this| {
this.hover(|this| this.border_color(theme.blue))
.active(|this| this.bg(theme.blue.lighten(10.0)))
})
.when_some(
self.on_click.filter(|_| !self.disabled),

View file

@ -1,15 +1,30 @@
use gpui::{
div, ClickEvent, IntoElement, ParentElement as _, Render, Styled as _, ViewContext,
VisualContext as _, WindowContext,
WindowContext,
};
use crate::text_field::TextField;
use super::story_case;
pub struct InputStory;
pub struct InputStory {
input1: TextField,
input2: TextField,
}
impl InputStory {
pub(crate) fn new(cx: &mut ViewContext<Self>) -> Self {
let input1 = TextField::new("Enter text here...", false, cx);
input1
.view
.update(cx, |text_view, cx| text_view.set_text("Hello 世界", cx));
Self {
input1,
input2: TextField::new("Enter text here...", true, cx),
}
}
#[allow(unused)]
fn on_change(ev: &ClickEvent, cx: &mut WindowContext) {
println!("Input changed: {:?}", ev);
@ -18,14 +33,17 @@ impl InputStory {
impl Render for InputStory {
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
let input1 = self.input1.clone();
let input2 = self.input2.clone();
story_case("Input", "A text input field.").child(
div()
.flex()
.flex_col()
.justify_start()
.gap_3()
.child(cx.new_view(|cx| TextField::new("Enter text here...", false, cx)))
.child(cx.new_view(|cx| TextField::new("Enter text here...", false, cx))),
.child(input1)
.child(input2),
)
}
}

View file

@ -2,8 +2,8 @@ use core::fmt;
use std::fmt::{Display, Formatter};
use gpui::{
div, prelude::FluentBuilder as _, px, AnyElement, ElementId, IntoElement, ParentElement,
Render, RenderOnce, SharedString, Styled as _, View, ViewContext, VisualContext, WindowContext,
div, prelude::FluentBuilder as _, px, AnyElement, IntoElement, ParentElement, Render,
RenderOnce, SharedString, Styled as _, View, ViewContext, VisualContext, WindowContext,
};
mod button_story;
@ -76,22 +76,26 @@ impl Display for StoryType {
pub struct Stories {
active: StoryType,
button_story: View<ButtonStory>,
input_story: View<InputStory>,
}
impl Stories {
fn new() -> Self {
fn new(cx: &mut ViewContext<Self>) -> Self {
Self {
active: StoryType::Button,
button_story: cx.new_view(|cx| ButtonStory {}),
input_story: cx.new_view(InputStory::new),
}
}
pub fn view(cx: &mut WindowContext) -> View<Self> {
cx.new_view(|_cx| Self::new())
cx.new_view(Self::new)
}
fn set_active(&mut self, ty: StoryType, cx: &mut ViewContext<Self>) {
self.active = ty;
dbg!("--------------------- set_active: {}", ty);
cx.notify();
}
@ -111,20 +115,12 @@ impl Stories {
cx: &mut ViewContext<Self>,
) -> impl IntoElement {
let name = format!("{}", ty);
Button::new(SharedString::from(id.to_string()), name)
.on_click(move |_e, cx| {
dbg!("--------------------- on_click: {}", ty);
// cx.update_view(self, |this| {
// this.set_active(ty, cx);
// });
})
.style(crate::button::ButtonStyle::Secondary)
}
}
impl Default for Stories {
fn default() -> Self {
Self::new()
Button::new(SharedString::from(id.to_string()), name)
.on_click(cx.listener(move |this, _, cx| {
this.set_active(ty, cx);
}))
.style(crate::button::ButtonStyle::Secondary)
}
}
@ -136,8 +132,8 @@ impl Render for Stories {
.gap_4()
.child(self.render_story_buttons(cx))
.map(|this| match self.active {
StoryType::Button => this.child(cx.new_view(|_cx| ButtonStory {})),
StoryType::Input => this.child(cx.new_view(|_cx| InputStory {})),
StoryType::Button => this.child(self.button_story.clone()),
StoryType::Input => this.child(self.input_story.clone()),
})
}
}

View file

@ -5,16 +5,16 @@ 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,
div, ClipboardItem, Context, EventEmitter, FocusHandle, InteractiveElement, IntoElement,
KeyDownEvent, Model, ParentElement, Render, RenderOnce, Styled, View, ViewContext,
WindowContext,
};
use std::{ops::Range, time::Duration};
use std::time::Duration;
use text_view::TextView;
const CURSOR_BLINK_INTERVAL: Duration = Duration::from_millis(500);
#[derive(Clone)]
#[derive(Clone, IntoElement)]
pub struct TextField {
focus_handle: FocusHandle,
disable: bool,
@ -23,14 +23,14 @@ pub struct TextField {
}
impl TextField {
pub fn new(placeholder: &str, disable: bool, cx: &mut ViewContext<Self>) -> Self {
pub fn new(placeholder: &str, disable: bool, cx: &mut WindowContext) -> 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();
// cx.on_focus(&focus_handle, Self::handle_focus).detach();
// cx.on_blur(&focus_handle, Self::handle_blur).detach();
Self {
focus_handle,
@ -67,42 +67,34 @@ impl Disableable for TextField {
self
}
}
impl Render for TextField {
fn render<'a>(&mut self, cx: &mut ViewContext<'a, Self>) -> impl IntoElement {
impl RenderOnce for TextField {
fn render(self, cx: &mut WindowContext) -> impl IntoElement {
cx.focus(&self.focus_handle);
let theme = cx.global::<Theme>();
let text_view = self.view.clone();
let text_view1 = text_view.clone();
let view = self.view.clone();
div()
.border_color(if self.focus_handle.is_focused(cx) {
theme.blue
} else {
theme.crust
})
.border_1()
.track_focus(&self.focus_handle)
.on_key_down(move |ev, cx| {
text_view1.update(cx, |editor, cx| {
let prev = editor.text.clone();
self.view.update(cx, |text_view, cx| {
let prev = text_view.text.clone();
cx.emit(TextEvent::KeyDown(ev.clone()));
let keystroke = &ev.keystroke.key;
let chars = editor.text.chars().collect::<Vec<char>>();
let chars = text_view.text.chars().collect::<Vec<char>>();
let m = ev.keystroke.modifiers.secondary();
dbg!("---------------- {:?}", ev);
dbg!(&text_view.text, &text_view.selection);
if m {
match keystroke.as_str() {
"a" => {
editor.selection = 0..chars.len();
text_view.selection = 0..chars.len();
}
"c" => {
// if !editor.masked {
// if !text_view.masked {
let selected_text =
chars[editor.selection.clone()].iter().collect();
chars[text_view.selection.clone()].iter().collect();
cx.write_to_clipboard(ClipboardItem::new(selected_text));
// }
}
@ -110,96 +102,107 @@ impl Render for TextField {
let clipboard = cx.read_from_clipboard();
if let Some(clipboard) = clipboard {
let text = clipboard.text();
editor.text.replace_range(
editor.char_range_to_text_range(&editor.text),
text_view.text.replace_range(
text_view.char_range_to_text_range(&text_view.text),
text,
);
let i = editor.selection.start + text.chars().count();
editor.selection = i..i;
let i = text_view.selection.start + text.chars().count();
text_view.selection = i..i;
}
}
"x" => {
let selected_text =
chars[editor.selection.clone()].iter().collect();
chars[text_view.selection.clone()].iter().collect();
cx.write_to_clipboard(ClipboardItem::new(selected_text));
editor.text.replace_range(
editor.char_range_to_text_range(&editor.text),
text_view.text.replace_range(
text_view.char_range_to_text_range(&text_view.text),
"",
);
editor.selection.end = editor.selection.start;
text_view.selection.end = text_view.selection.start;
}
_ => {}
}
} else if !ev.keystroke.ime_key.clone().unwrap_or_default().is_empty() {
let ime_key = &ev.keystroke.ime_key.clone().unwrap_or_default();
editor
.text
.replace_range(editor.char_range_to_text_range(&editor.text), ime_key);
let i = editor.selection.start + ime_key.chars().count();
editor.selection = i..i;
text_view.text.replace_range(
text_view.char_range_to_text_range(&text_view.text),
ime_key,
);
let i = text_view.selection.start + ime_key.chars().count();
text_view.selection = i..i;
} else {
match keystroke.as_str() {
"left" => {
if editor.selection.start > 0 {
let i = if editor.selection.start == editor.selection.end {
editor.selection.start - 1
if text_view.selection.start > 0 {
let i = if text_view.selection.start == text_view.selection.end
{
text_view.selection.start - 1
} else {
editor.selection.start
text_view.selection.start
};
editor.selection = i..i;
text_view.selection = i..i;
}
}
"right" => {
if editor.selection.end < editor.text.len() {
let i = if editor.selection.start == editor.selection.end {
editor.selection.end + 1
if text_view.selection.end < text_view.text.len() {
let i = if text_view.selection.start == text_view.selection.end
{
text_view.selection.end + 1
} else {
editor.selection.end
text_view.selection.end
};
editor.selection = i..i;
text_view.selection = i..i;
}
}
"backspace" => {
if editor.text.is_empty() && !ev.is_held {
if text_view.text.is_empty() && !ev.is_held {
// cx.emit(TextEvent::Back);
} else if editor.selection.start == editor.selection.end
&& editor.selection.start > 0
} else if text_view.selection.start == text_view.selection.end
&& text_view.selection.start > 0
{
let i = (editor.selection.start - 1).min(chars.len());
editor.text = chars[0..i].iter().collect::<String>()
+ &(chars[editor.selection.end.min(chars.len())..]
let i = (text_view.selection.start - 1).min(chars.len());
text_view.text = chars[0..i].iter().collect::<String>()
+ &(chars[text_view.selection.end.min(chars.len())..]
.iter()
.collect::<String>());
editor.selection = i..i;
text_view.selection = i..i;
} else {
editor.text.replace_range(
editor.char_range_to_text_range(&editor.text),
text_view.text.replace_range(
text_view.char_range_to_text_range(&text_view.text),
"",
);
editor.selection.end = editor.selection.start;
text_view.selection.end = text_view.selection.start;
}
}
"enter" => {
if ev.keystroke.modifiers.shift {
editor.text.insert(
editor.char_range_to_text_range(&editor.text).start,
text_view.text.insert(
text_view.char_range_to_text_range(&text_view.text).start,
'\n',
);
let i = editor.selection.start + 1;
editor.selection = i..i;
let i = text_view.selection.start + 1;
text_view.selection = i..i;
}
}
_ => {}
};
}
if prev != editor.text {
if prev != text_view.text {
cx.emit(TextEvent::Input {
text: editor.text.clone(),
text: text_view.text.clone(),
});
dbg!(&text_view.text, &text_view.selection);
}
cx.notify();
});
})
.border_color(if self.focus_handle.is_focused(cx) {
theme.blue
} else {
theme.crust
})
.border_1()
.rounded_sm()
.py_1p5()
.px_3()
@ -209,7 +212,7 @@ impl Render for TextField {
} else {
theme.base
})
.child(text_view)
.child(view)
}
}

View file

@ -1,16 +1,14 @@
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 std::ops::Range;
use super::{
blink_manager::BlinkManager, cursor_layout::CursorLayout, TextEvent, CURSOR_BLINK_INTERVAL,
};
use crate::{hls, theme::Theme};
use gpui::{
px, relative, Context, Element, EventEmitter, FocusHandle, HighlightStyle, InteractiveText,
IntoElement, Model, Render, Style, StyledText, TextStyle, View, ViewContext, VisualContext,
WindowContext,
};
pub struct TextView {
pub text: String,
@ -20,6 +18,7 @@ pub struct TextView {
pub disable: bool,
pub blink_manager: Model<BlinkManager>,
pub cursor: CursorLayout,
pub masked: bool,
}
impl EventEmitter<TextEvent> for TextView {}
@ -49,6 +48,7 @@ impl TextView {
blink_manager,
cursor,
disable,
masked: false,
};
let view = cx.new_view(|cx| {
@ -74,7 +74,7 @@ impl TextView {
&view,
move |subscriber, emitter: &TextEvent, cx| match emitter {
TextEvent::Input { text: _ } => {
subscriber.update(cx, |editor, cx| {
subscriber.update(cx, |editor, _cx| {
editor.word_click = (0, 0);
});
}
@ -184,7 +184,7 @@ impl Element for TextView {
let mut style = Style::default();
style.size.width = relative(1.).into();
style.size.height = relative(24.).into();
dbg!("--------- request_layout", &style);
(cx.request_layout(style, None), ())
}
@ -216,6 +216,7 @@ 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();
dbg!("textView render", &text);
let mut style = TextStyle {
color: theme.text,
@ -223,21 +224,24 @@ impl Render for TextView {
..Default::default()
};
if self.masked {
text = "".repeat(text.len());
}
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 mut highlights = vec![(self.char_range_to_text_range(&text), selection_style)];
let styled_text: StyledText = if text.is_empty() {
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)
};
highlights = vec![];
}
let styled_text = 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 file

@ -1,7 +1,14 @@
use gpui::{prelude::FluentBuilder, *};
use std::sync::Arc;
use ui::{button::Button, disableable::Clickable as _, text_field::TextField, theme::Theme, Color};
use ui::{
button::Button,
disableable::Clickable as _,
story::Stories,
text_field::{self, TextField},
theme::Theme,
Color,
};
use util::ResultExt as _;
mod app_state;
@ -11,6 +18,7 @@ pub use app_state::AppState;
pub struct Workspace {
weak_self: WeakView<Self>,
stories: View<Stories>,
}
impl Workspace {
@ -23,6 +31,7 @@ impl Workspace {
Workspace {
weak_self: weak_handle.clone(),
stories: Stories::view(cx),
}
}
@ -82,8 +91,6 @@ pub fn open_new(
})
}
impl Workspace {}
impl Render for Workspace {
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
let theme = cx.global::<Theme>();
@ -113,20 +120,6 @@ impl Render for Workspace {
.on_click(|_e, cx| Theme::change(ui::theme::ThemeMode::Dark, cx)),
),
)
.child(
div()
.flex()
.py_3()
.gap_2()
.child(ui::story::Stories::view(cx)),
)
.child({
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
})
})
.child(div().flex().py_3().gap_2().child(self.stories.clone()))
}
}