From ae5e4f61490bdd4cb0681aaa58e219a5854b3f5b Mon Sep 17 00:00:00 2001 From: Jason Lee Date: Sat, 22 Jun 2024 21:47:04 +0800 Subject: [PATCH] Update input --- crates/app/src/main.rs | 2 +- crates/ui/src/button.rs | 45 +++-- crates/ui/src/label.rs | 16 +- crates/ui/src/story/button_story.rs | 2 +- crates/ui/src/story/input_story.rs | 14 +- crates/ui/src/story/mod.rs | 139 +++++++++++++- crates/ui/src/story/story.rs | 127 ------------- crates/ui/src/text_field.rs | 8 +- crates/ui/src/text_field/blink_manager.rs | 13 +- crates/ui/src/text_field/cursor_layout.rs | 3 +- crates/ui/src/text_field/text_field.rs | 210 +++++++++++++--------- crates/ui/src/theme.rs | 14 +- crates/workspace/src/lib.rs | 72 +++++--- 13 files changed, 376 insertions(+), 289 deletions(-) delete mode 100644 crates/ui/src/story/story.rs diff --git a/crates/app/src/main.rs b/crates/app/src/main.rs index 81eb79ed..1c2844fa 100644 --- a/crates/app/src/main.rs +++ b/crates/app/src/main.rs @@ -26,7 +26,7 @@ fn main() { return; } - workspace::open_new(app_state.clone(), cx, |workspace, cx| { + workspace::open_new(app_state.clone(), cx, |_workspace, _cx| { // do something }) .detach(); diff --git a/crates/ui/src/button.rs b/crates/ui/src/button.rs index 73f626a7..b6c78c1e 100644 --- a/crates/ui/src/button.rs +++ b/crates/ui/src/button.rs @@ -8,6 +8,7 @@ use crate::{ colors::Color, disableable::{Clickable, Disableable, Selectable}, label::Label, + theme::Theme, HlsaExt as _, }; @@ -117,26 +118,17 @@ impl Clickable for Button { impl RenderOnce for Button { fn render(self, cx: &mut WindowContext) -> impl IntoElement { + let theme = cx.global::(); let style: ButtonStyle = self.style; - let normal_style = style.normal(cx); self.base .id(self.id) - .group("") .flex() .items_center() .justify_center() - .child( - Label::new(self.label) - .color(style.text_color()) - .map(|this| match self.size { - ButtonSize::Small => this.text_sm(), - ButtonSize::Medium => this.text_base(), - }), - ) .map(|this| match self.size { - ButtonSize::Small => this.px_3().py_2().h_7(), - ButtonSize::Medium => this.px_4().py_2().h_10(), + ButtonSize::Small => this.px_3().py_2().h_5(), + ButtonSize::Medium => this.px_4().py_2().h_8(), }) .map(|this| match self.rounded { ButtonRounded::Small => this.rounded_sm(), @@ -145,22 +137,13 @@ impl RenderOnce for Button { ButtonRounded::None => this.rounded_none(), }) .when(!self.disabled, |this| { - this.cursor_pointer() - .hover(|this| { - let hover_style = style.hovered(cx); - this.bg(hover_style.bg).border_color(hover_style.border) - }) - .active(|this| { - let active_style = style.active(cx); - this.bg(active_style.bg).border_color(active_style.border) - }) + this.hover(|this| this.border_color(theme.blue)) }) .when_some( self.on_click.filter(|_| !self.disabled), |this, on_click| { this.on_mouse_down(MouseButton::Left, |_, cx| cx.prevent_default()) .on_click(move |event, cx| { - dbg!("---------- button click"); cx.stop_propagation(); (on_click)(event, cx) }) @@ -173,8 +156,22 @@ impl RenderOnce for Button { .border_color(disabled_style.border) }) .border_1() - .border_color(normal_style.border) - .bg(normal_style.bg) + .border_color(theme.crust) + .bg(theme.base) + .child({ + let text_color = if self.disabled { + theme.text_disabled + } else { + theme.text + }; + + Label::new(self.label) + .color(text_color) + .map(|this| match self.size { + ButtonSize::Small => this.text_sm(), + ButtonSize::Medium => this.text_base(), + }) + }) } } diff --git a/crates/ui/src/label.rs b/crates/ui/src/label.rs index 801f4c19..ff3c74c3 100644 --- a/crates/ui/src/label.rs +++ b/crates/ui/src/label.rs @@ -1,15 +1,15 @@ use gpui::{ - div, prelude::FluentBuilder as _, AbsoluteLength, DefiniteLength, Div, IntoElement, - ParentElement, RenderOnce, SharedString, Style, StyleRefinement, Styled, WindowContext, + div, prelude::FluentBuilder as _, AbsoluteLength, DefiniteLength, Div, Half, Hsla, IntoElement, + ParentElement, RenderOnce, SharedString, Styled, WindowContext, }; -use crate::colors::Color; +use crate::{hls, theme::Theme}; #[derive(IntoElement)] pub struct Label { base: Div, label: SharedString, - color: Color, + color: Hsla, multiple_lines: bool, line_height: Option, text_size: Option, @@ -21,7 +21,7 @@ impl Label { base: div(), label: label.into(), multiple_lines: false, - color: Color::Foreground, + color: hls(0., 0., 0.), line_height: None, text_size: None, } @@ -32,7 +32,7 @@ impl Label { self } - pub fn color(mut self, color: Color) -> Self { + pub fn color(mut self, color: Hsla) -> Self { self.color = color; self } @@ -46,6 +46,8 @@ impl Styled for Label { impl RenderOnce for Label { fn render(self, cx: &mut WindowContext) -> impl IntoElement { + let theme = cx.global::(); + let label_text = if !self.multiple_lines { SharedString::from(self.label.replace('\n', "␤")) } else { @@ -54,7 +56,7 @@ impl RenderOnce for Label { self.base .child(label_text) - .text_color(self.color.color(cx)) + .text_color(theme.text) .map(|this| { if let Some(text_size) = self.text_size { this.text_size(text_size) diff --git a/crates/ui/src/story/button_story.rs b/crates/ui/src/story/button_story.rs index 9dcde8d5..7ff7e54a 100644 --- a/crates/ui/src/story/button_story.rs +++ b/crates/ui/src/story/button_story.rs @@ -5,7 +5,7 @@ use gpui::{ use crate::{ button::{Button, ButtonSize, ButtonStyle}, - disableable::{Clickable as _, Disableable as _}, + disableable::{Clickable, Disableable as _}, }; use super::story_case; diff --git a/crates/ui/src/story/input_story.rs b/crates/ui/src/story/input_story.rs index d4d889a9..f96565fc 100644 --- a/crates/ui/src/story/input_story.rs +++ b/crates/ui/src/story/input_story.rs @@ -1,6 +1,6 @@ use gpui::{ - div, prelude::FluentBuilder as _, ClickEvent, IntoElement, ParentElement as _, Render, - Styled as _, ViewContext, VisualContext, WindowContext, + div, ClickEvent, IntoElement, ParentElement as _, Render, Styled as _, ViewContext, + WindowContext, }; use crate::text_field::TextField; @@ -10,6 +10,7 @@ use super::story_case; pub struct InputStory; impl InputStory { + #[allow(unused)] fn on_change(ev: &ClickEvent, cx: &mut WindowContext) { println!("Input changed: {:?}", ev); } @@ -23,13 +24,8 @@ impl Render for InputStory { .flex_col() .justify_start() .gap_3() - .child({ - TextField::new(cx, "Enter text here...", false) - }) - .child({ - let input = TextField::new(cx, "Enter text here...", false); - input - }), + .child(TextField::new(cx, "Enter text here...", false)) + .child(TextField::new(cx, "Enter text here...", false)), ) } } diff --git a/crates/ui/src/story/mod.rs b/crates/ui/src/story/mod.rs index cbcad143..a1fb2555 100644 --- a/crates/ui/src/story/mod.rs +++ b/crates/ui/src/story/mod.rs @@ -1,10 +1,143 @@ +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, +}; + mod button_story; mod input_story; -mod story; -pub use story::Stories; -use story::StoryContainer; +use crate::{button::Button, disableable::Clickable as _, label::Label}; + +use button_story::ButtonStory; +use input_story::InputStory; pub fn story_case(name: &'static str, description: &'static str) -> StoryContainer { StoryContainer::new(name, description) } + +#[derive(IntoElement)] +pub struct StoryContainer { + name: SharedString, + description: SharedString, + children: Vec, +} + +impl ParentElement for StoryContainer { + fn extend(&mut self, elements: impl IntoIterator) { + self.children.extend(elements) + } +} + +impl StoryContainer { + pub fn new(name: impl Into, description: impl Into) -> Self { + Self { + name: name.into(), + description: description.into(), + children: Vec::new(), + } + } +} + +impl RenderOnce for StoryContainer { + fn render(self, _cx: &mut WindowContext) -> impl IntoElement { + div() + .flex() + .flex_col() + .gap_4() + .child( + div() + .flex() + .flex_col() + .gap_2() + .child(Label::new(self.name).text_size(px(24.0))) + .child(Label::new(self.description).text_size(px(16.0))), + ) + .children(self.children) + } +} + +#[derive(Clone, Copy, PartialEq, Eq, Debug)] +enum StoryType { + Button, + Input, +} + +impl Display for StoryType { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + match self { + Self::Button => write!(f, "Button"), + Self::Input => write!(f, "Input"), + } + } +} + +pub struct Stories { + active: StoryType, +} + +impl Stories { + fn new() -> Self { + Self { + active: StoryType::Button, + } + } + + pub fn view(cx: &mut WindowContext) -> View { + cx.new_view(|_cx| Self::new()) + } + + fn set_active(&mut self, ty: StoryType, cx: &mut ViewContext) { + self.active = ty; + dbg!("--------------------- set_active: {}", ty); + cx.notify(); + } + + fn render_story_buttons(&self, cx: &mut ViewContext) -> impl IntoElement { + div() + .flex() + .items_center() + .gap_4() + .child(self.swith_button("story-button", StoryType::Button, cx)) + .child(self.swith_button("story-input", StoryType::Input, cx)) + } + + fn swith_button( + &self, + id: &str, + ty: StoryType, + cx: &mut ViewContext, + ) -> 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() + } +} + +impl Render for Stories { + fn render(&mut self, cx: &mut ViewContext) -> impl IntoElement { + div() + .flex() + .flex_col() + .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 {})), + }) + } +} diff --git a/crates/ui/src/story/story.rs b/crates/ui/src/story/story.rs deleted file mode 100644 index c72880fd..00000000 --- a/crates/ui/src/story/story.rs +++ /dev/null @@ -1,127 +0,0 @@ -use core::fmt; -use std::fmt::{Display, Formatter}; - -use gpui::{ - div, prelude::FluentBuilder as _, px, AnyElement, ElementId, InputHandler, IntoElement, - ParentElement, Render, RenderOnce, SharedString, Styled as _, ViewContext, VisualContext, - WindowContext, -}; - -use crate::{button::Button, disableable::Clickable as _, label::Label}; - -use super::{button_story::ButtonStory, input_story::InputStory}; - -#[derive(IntoElement)] -pub struct StoryContainer { - name: SharedString, - description: SharedString, - children: Vec, -} - -impl ParentElement for StoryContainer { - fn extend(&mut self, elements: impl IntoIterator) { - self.children.extend(elements) - } -} - -impl StoryContainer { - pub fn new(name: impl Into, description: impl Into) -> Self { - Self { - name: name.into(), - description: description.into(), - children: Vec::new(), - } - } -} - -impl RenderOnce for StoryContainer { - fn render(self, _cx: &mut WindowContext) -> impl IntoElement { - div() - .flex() - .flex_col() - .gap_4() - .child( - div() - .flex() - .flex_col() - .gap_2() - .child(Label::new(self.name).text_size(px(24.0))) - .child(Label::new(self.description).text_size(px(16.0))), - ) - .children(self.children) - } -} - -#[derive(Clone, Copy, PartialEq, Eq, Debug)] -enum StoryType { - Button, - Input, -} - -impl Display for StoryType { - fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - match self { - Self::Button => write!(f, "Button"), - Self::Input => write!(f, "Input"), - } - } -} - -pub struct Stories { - active: StoryType, -} - -impl Stories { - pub fn new() -> Self { - Self { - active: StoryType::Input, - } - } - - fn set_active(&mut self, ty: StoryType, cx: &mut ViewContext) { - self.active = ty; - dbg!("--------------------- set_active: {}", ty); - cx.notify(); - } - - fn render_story_buttons(&self, cx: &mut ViewContext) -> impl IntoElement { - div() - .flex() - .items_center() - .gap_4() - .child(self.swith_button(StoryType::Button, cx)) - .child(self.swith_button(StoryType::Input, cx)) - } - - fn swith_button(&self, ty: StoryType, cx: &mut ViewContext) -> impl IntoElement { - let name = format!("{}", ty); - Button::new(ElementId::Name(SharedString::from(name.clone())), name) - .on_click(cx.listener(move |this, _, cx| { - dbg!("--------------------- on_click: {}", ty); - this.set_active(ty, cx); - })) - .style(crate::button::ButtonStyle::Secondary) - } -} - -impl Default for Stories { - fn default() -> Self { - Self::new() - } -} - -impl Render for Stories { - fn render(&mut self, cx: &mut ViewContext) -> impl IntoElement { - let nav_buttons = self.render_story_buttons(cx); - - div() - .flex() - .flex_col() - .gap_4() - .child(nav_buttons) - .map(|this| match self.active { - StoryType::Button => this.child(cx.new_view(|cx| ButtonStory {})), - StoryType::Input => this.child(cx.new_view(|cx| InputStory {})), - }) - } -} diff --git a/crates/ui/src/text_field.rs b/crates/ui/src/text_field.rs index db9d4046..e26c7945 100644 --- a/crates/ui/src/text_field.rs +++ b/crates/ui/src/text_field.rs @@ -1,5 +1,5 @@ -pub mod blink_manager; -pub mod cursor_layout; -pub mod text_field; +mod blink_manager; +mod cursor_layout; +mod text_field; -pub use text_field::*; \ No newline at end of file +pub use text_field::*; diff --git a/crates/ui/src/text_field/blink_manager.rs b/crates/ui/src/text_field/blink_manager.rs index 5e6d455e..8bf06b23 100644 --- a/crates/ui/src/text_field/blink_manager.rs +++ b/crates/ui/src/text_field/blink_manager.rs @@ -4,7 +4,6 @@ use gpui::ModelContext; pub struct BlinkManager { blink_interval: Duration, - blink_epoch: usize, blinking_paused: bool, visible: bool, @@ -14,7 +13,7 @@ pub struct BlinkManager { impl BlinkManager { pub fn new(blink_interval: Duration) -> Self { Self { - blink_interval: Duration::from_millis(500), + blink_interval, blink_epoch: 0, blinking_paused: false, visible: true, @@ -22,11 +21,17 @@ impl BlinkManager { } } - pub fn show_cursor(&self, cx: &mut ModelContext<'_, Self>) -> bool { + pub fn show_cursor(&self, _cx: &mut ModelContext<'_, Self>) -> bool { self.enabled && (!self.blinking_paused || self.visible) } - pub fn blink_cursor(&mut self, epoch: usize, cx: &mut ModelContext) {} + pub fn blink_cursor(&mut self, epoch: usize, cx: &mut ModelContext) { + if self.blink_epoch != epoch { + self.blink_epoch = epoch; + self.visible = !self.visible; + cx.refresh(); + } + } pub fn disable(&mut self, _cx: &mut ModelContext) { self.enabled = false; diff --git a/crates/ui/src/text_field/cursor_layout.rs b/crates/ui/src/text_field/cursor_layout.rs index faed7765..b78a4766 100644 --- a/crates/ui/src/text_field/cursor_layout.rs +++ b/crates/ui/src/text_field/cursor_layout.rs @@ -1,7 +1,8 @@ -use gpui::{outline, px, AppContext, Bounds, Hsla, Pixels, ShapedLine, Size, ViewContext}; +use gpui::{outline, px, Bounds, Hsla, Pixels, ShapedLine, Size, ViewContext}; pub struct CursorLayout { origin: gpui::Point, + #[allow(unused)] block_width: Pixels, line_height: Pixels, color: Hsla, diff --git a/crates/ui/src/text_field/text_field.rs b/crates/ui/src/text_field/text_field.rs index 8769c553..a7c85a0e 100644 --- a/crates/ui/src/text_field/text_field.rs +++ b/crates/ui/src/text_field/text_field.rs @@ -1,12 +1,12 @@ use std::{ops::Range, time::Duration}; use gpui::{ - div, Context, EventEmitter, FocusHandle, HighlightStyle, InteractiveElement, InteractiveText, - IntoElement, KeyDownEvent, Model, ParentElement, Render, RenderOnce, Styled, StyledText, - TextStyle, View, ViewContext, VisualContext, WindowContext, + div, ClipboardItem, Context, EventEmitter, FocusHandle, HighlightStyle, InteractiveElement, + InteractiveText, IntoElement, KeyDownEvent, Model, ParentElement, Render, RenderOnce, Styled, + StyledText, TextStyle, View, ViewContext, VisualContext, WindowContext, }; -use crate::{theme::Theme, disableable::Disableable}; +use crate::{disableable::Disableable, theme::Theme}; use super::{blink_manager::BlinkManager, cursor_layout::CursorLayout}; @@ -43,107 +43,144 @@ impl Disableable for TextField { impl RenderOnce for TextField { fn render(self, cx: &mut WindowContext) -> impl IntoElement { - // cx.focus(&self.focus_handle); + cx.focus(&self.focus_handle); let theme = cx.global::(); let clone = self.view.clone(); div() - .border_color( - self.focus_handle - .is_focused(cx) - .then(|| theme.blue) - .unwrap_or(theme.crust), - ) + .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 |event, cx| { - if self.disable { - return; - } + .on_key_down(move |ev, cx| { + self.view.update(cx, |editor, cx| { + let prev = editor.text.clone(); + cx.emit(TextEvent::KeyDown(ev.clone())); + let keystroke = &ev.keystroke.key; + let chars = editor.text.chars().collect::>(); + let m = ev.keystroke.modifiers.secondary(); - self.view.update(cx, |text_view, vc| { - let prev = text_view.text.clone(); - vc.emit(TextEvent::KeyDown(event.clone())); - let keystroke = &event.keystroke.key; - let chars = text_view.text.chars().collect::>(); - - let m = event.keystroke.modifiers.platform; + dbg!("---------------- {:?}", ev); if m { match keystroke.as_str() { + "a" => { + editor.selection = 0..chars.len(); + } + "c" => { + // if !editor.masked { + let selected_text = + chars[editor.selection.clone()].iter().collect(); + cx.write_to_clipboard(ClipboardItem::new(selected_text)); + // } + } + "v" => { + 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, + ); + let i = editor.selection.start + text.chars().count(); + editor.selection = i..i; + } + } + "x" => { + let selected_text = + chars[editor.selection.clone()].iter().collect(); + cx.write_to_clipboard(ClipboardItem::new(selected_text)); + editor.text.replace_range( + editor.char_range_to_text_range(&editor.text), + "", + ); + editor.selection.end = editor.selection.start; + } _ => {} } - } else if !event - .keystroke - .ime_key - .clone() - .unwrap_or_default() - .is_empty() - { - let ime_key = &event.keystroke.ime_key.clone().unwrap_or_default(); - 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 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; } else { match keystroke.as_str() { "left" => { - if text_view.selection.start > 0 { - text_view.selection = - text_view.selection.start - 1..text_view.selection.end; + if editor.selection.start > 0 { + let i = if editor.selection.start == editor.selection.end { + editor.selection.start - 1 + } else { + editor.selection.start + }; + editor.selection = i..i; } } "right" => { - if text_view.selection.end < text_view.text.len() { - text_view.selection = - text_view.selection.start + 1..text_view.selection.end + 1; - } else { - text_view.selection = - text_view.selection.start + 1..text_view.selection.end; + if editor.selection.end < editor.text.len() { + let i = if editor.selection.start == editor.selection.end { + editor.selection.end + 1 + } else { + editor.selection.end + }; + editor.selection = i..i; } } "backspace" => { - if text_view.text.is_empty() { - return; - } - - if text_view.selection.start == text_view.selection.end { - let i = (text_view.selection.start - 1).min(chars.len()); - text_view.text = chars[0..i].iter().collect::() - + &(chars[text_view.selection.end.min(chars.len())..] + if editor.text.is_empty() && !ev.is_held { + // cx.emit(TextEvent::Back); + } else if editor.selection.start == editor.selection.end + && editor.selection.start > 0 + { + let i = (editor.selection.start - 1).min(chars.len()); + editor.text = chars[0..i].iter().collect::() + + &(chars[editor.selection.end.min(chars.len())..] .iter() .collect::()); - text_view.selection = i..i; + editor.selection = i..i; + } else { + editor.text.replace_range( + editor.char_range_to_text_range(&editor.text), + "", + ); + editor.selection.end = editor.selection.start; + } + } + "enter" => { + if ev.keystroke.modifiers.shift { + editor.text.insert( + editor.char_range_to_text_range(&editor.text).start, + '\n', + ); + let i = editor.selection.start + 1; + editor.selection = i..i; } - - text_view.text.replace_range( - text_view.char_range_to_text_range(&text_view.text), - "", - ); - - text_view.selection.end = text_view.selection.start; } _ => {} - } + }; } - - if prev != text_view.text { - vc.emit(TextEvent::Input { - text: text_view.text.clone(), + if prev != editor.text { + cx.emit(TextEvent::Input { + text: editor.text.clone(), }); } - - vc.notify(); - }) + cx.notify(); + }); }) - .rounded_lg() + .rounded_sm() .py_1p5() .px_3() .min_w_20() - .bg(self.disable.then(|| theme.crust).unwrap_or(theme.base)) + .bg(if self.disable { + theme.crust + } else { + theme.base + }) .child(clone) } } @@ -175,7 +212,7 @@ impl TextView { placeholder: &str, disable: bool, ) -> View { - let blink_manager = cx.new_model(|cx| BlinkManager::new(CURSOR_BLINK_INTERVAL)); + 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)), @@ -203,15 +240,17 @@ impl TextView { let view = cx.new_view(|cx| { cx.on_blur( focus_handle, - |editor: &mut TextView, cx: &mut ViewContext<'_, TextView>| { - editor.blink_manager.update(cx, BlinkManager::disable); + |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.select_all(cx); + view.blink_manager.update(cx, |bm, cx| { + bm.blink_cursor(0, cx); + }); }) .detach(); m @@ -285,6 +324,15 @@ impl TextView { .len(); start..end } + + pub fn set_text(&mut self, text: impl ToString, cx: &mut ViewContext) { + 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 { @@ -292,9 +340,11 @@ impl Render for TextView { let theme = cx.global::(); let mut text = self.text.clone(); - let mut style = TextStyle::default(); - style.color = theme.text; - style.font_family = theme.font_sans.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; @@ -303,10 +353,10 @@ impl Render for TextView { let highlights = vec![(self.char_range_to_text_range(&text), selection_style)]; - let styled_text: StyledText = if text.len() == 0 { + let styled_text: StyledText = if text.is_empty() { text = self.placeholder.to_string(); style.color = theme.subtext0; - StyledText::new(text) + StyledText::new(text).with_highlights(&style, highlights) } else { StyledText::new(text).with_highlights(&style, highlights) }; diff --git a/crates/ui/src/theme.rs b/crates/ui/src/theme.rs index 5780e6d1..f831c9c0 100644 --- a/crates/ui/src/theme.rs +++ b/crates/ui/src/theme.rs @@ -64,16 +64,26 @@ impl From for Theme { } } +pub enum ThemeMode { + Light, + Dark, +} + impl Theme { fn new() -> Self { - Self::from(catppuccin::PALETTE.mocha.colors) + Self::from(catppuccin::PALETTE.latte.colors) } pub fn init(cx: &mut AppContext) { cx.set_global(Theme::new()) } - pub fn change(flavour: Flavor, cx: &mut AppContext) { + pub fn change(mode: ThemeMode, cx: &mut AppContext) { + let flavour = match mode { + ThemeMode::Light => catppuccin::PALETTE.latte, + ThemeMode::Dark => catppuccin::PALETTE.mocha, + }; + cx.set_global(Self::from(flavour.colors)); cx.refresh(); } diff --git a/crates/workspace/src/lib.rs b/crates/workspace/src/lib.rs index 8702f3cc..e419f0e1 100644 --- a/crates/workspace/src/lib.rs +++ b/crates/workspace/src/lib.rs @@ -2,7 +2,11 @@ use gpui::{prelude::FluentBuilder, *}; use std::sync::Arc; use ui::{ - button::{Button, ButtonStyle}, disableable::Clickable as _, theme::Theme, Color + button::{Button, ButtonStyle}, + disableable::Clickable as _, + text_field::TextField, + theme::Theme, + Color, }; use util::ResultExt as _; @@ -17,17 +21,15 @@ pub struct Workspace { impl Workspace { pub fn new( - app_state: Arc, - parent: Option>, + _app_state: Arc, + _parent: Option>, cx: &mut ViewContext, ) -> Self { let weak_handle = cx.view().downgrade(); - let workspace = Workspace { + Workspace { weak_self: weak_handle.clone(), - }; - - workspace + } } pub fn new_local( @@ -42,15 +44,19 @@ impl Workspace { ..Default::default() }; - let window = cx.open_window(options, { - let app_state = app_state.clone(); - move |cx| cx.new_view(|cx| Workspace::new(app_state.clone(), None, cx)) + let window = cx.open_window(options, |cx| { + cx.new_view(|cx| Workspace::new(app_state.clone(), None, cx)) })?; window .update(&mut cx, |_, cx| { cx.activate_window(); cx.set_window_title("GPUI App"); + cx.on_release(|_, _, _cx| { + // exit app + std::process::exit(0); + }) + .detach(); }) .log_err(); @@ -61,11 +67,8 @@ impl Workspace { actions!(workspace, [Open]); -pub fn init(app_state: Arc, cx: &mut AppContext) { - cx.on_action({ - let app_state = app_state.clone(); - move |action: &Open, cx: &mut AppContext| {} - }); +pub fn init(_app_state: Arc, cx: &mut AppContext) { + cx.on_action(|_action: &Open, _cx: &mut AppContext| {}); Theme::init(cx); } @@ -85,18 +88,11 @@ pub fn open_new( }) } -impl Workspace { - pub fn render_ok_button(&mut self, cx: &mut ViewContext) -> impl IntoElement { - Button::new("ok-button", "OK") - .style(ButtonStyle::Primary) - .on_click(|_, cx| { - // todo - }) - } -} +impl Workspace {} impl Render for Workspace { fn render(&mut self, cx: &mut ViewContext) -> impl IntoElement { + let theme = cx.global::(); div() .relative() .flex() @@ -104,13 +100,37 @@ impl Render for Workspace { .flex_col() .size_full() .p_4() - .bg(Color::Background.color(cx)) + .bg(theme.base) + .gap_4() + .child( + div() + .flex() + .items_center() + .justify_end() + .gap_2() + .child( + Button::new("btn-light", "Light") + .size(ui::button::ButtonSize::Small) + .on_click(|_e, cx| Theme::change(ui::theme::ThemeMode::Light, cx)), + ) + .child( + Button::new("btn-dark", "Dark") + .size(ui::button::ButtonSize::Small) + .on_click(|_e, cx| Theme::change(ui::theme::ThemeMode::Dark, cx)), + ), + ) .child( div() .flex() .py_3() .gap_2() - .child(cx.new_view(|_| ui::story::Stories::new())), + .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 + }) } }