Update input new method
This commit is contained in:
parent
94cb714ebf
commit
dad6f4c431
5 changed files with 256 additions and 168 deletions
|
|
@ -1,6 +1,6 @@
|
|||
use gpui::{
|
||||
div, ClickEvent, IntoElement, ParentElement as _, Render, Styled as _, ViewContext,
|
||||
WindowContext,
|
||||
div, ClickEvent, IntoElement, ParentElement as _, Render, Styled as _, View, ViewContext,
|
||||
VisualContext, WindowContext,
|
||||
};
|
||||
|
||||
use crate::text_field::TextField;
|
||||
|
|
@ -10,18 +10,25 @@ use super::story_case;
|
|||
pub struct InputStory {
|
||||
input1: TextField,
|
||||
input2: TextField,
|
||||
mash_input: TextField,
|
||||
disabled_input: 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));
|
||||
pub(crate) fn new(cx: &mut WindowContext) -> Self {
|
||||
let input1 = TextField::new(cx).set_text("Hello 世界", cx);
|
||||
|
||||
let mask_input = TextField::new(cx)
|
||||
.set_masked(true, cx)
|
||||
.set_text("this-is-password", cx);
|
||||
|
||||
Self {
|
||||
input1,
|
||||
input2: TextField::new("Enter text here...", true, cx),
|
||||
input2: TextField::new(cx).set_placeholder("Enter text here...", cx),
|
||||
mash_input: mask_input,
|
||||
disabled_input: TextField::new(cx)
|
||||
.set_text("This is disabled input", cx)
|
||||
.set_disabled(true, cx),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -33,17 +40,16 @@ 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(input1)
|
||||
.child(input2),
|
||||
.child(self.input1.clone())
|
||||
.child(self.input2.clone())
|
||||
.child(self.disabled_input.clone())
|
||||
.child(self.mash_input.clone()),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -86,7 +86,7 @@ impl Stories {
|
|||
Self {
|
||||
active: StoryType::Button,
|
||||
button_story: cx.new_view(|cx| ButtonStory {}),
|
||||
input_story: cx.new_view(InputStory::new),
|
||||
input_story: cx.new_view(|cx| InputStory::new(cx)),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -8,9 +8,9 @@ use crate::{
|
|||
};
|
||||
use blink_manager::BlinkManager;
|
||||
use gpui::{
|
||||
div, ClipboardItem, Context, EventEmitter, FocusHandle, InteractiveElement, IntoElement,
|
||||
KeyDownEvent, Model, ParentElement, Render, RenderOnce, Styled, View, ViewContext,
|
||||
WindowContext,
|
||||
div, prelude::FluentBuilder as _, ClipboardItem, Context, Entity, EventEmitter, FocusHandle,
|
||||
InteractiveElement, IntoElement, KeyDownEvent, Model, MouseButton, ParentElement, RenderOnce,
|
||||
Styled, View, ViewContext, WindowContext,
|
||||
};
|
||||
use std::time::Duration;
|
||||
use text_view::TextView;
|
||||
|
|
@ -20,33 +20,53 @@ const CURSOR_BLINK_INTERVAL: Duration = Duration::from_millis(500);
|
|||
#[derive(Clone, IntoElement)]
|
||||
pub struct TextField {
|
||||
focus_handle: FocusHandle,
|
||||
disable: bool,
|
||||
blink_manager: Model<BlinkManager>,
|
||||
pub view: View<TextView>,
|
||||
}
|
||||
|
||||
impl TextField {
|
||||
pub fn new(placeholder: &str, disable: bool, cx: &mut WindowContext) -> Self {
|
||||
pub fn new(cx: &mut WindowContext) -> Self {
|
||||
let focus_handle = cx.focus_handle();
|
||||
let view = TextView::init(cx, &focus_handle, placeholder, disable);
|
||||
let view = TextView::init(cx, &focus_handle);
|
||||
|
||||
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,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn focus(&self, cx: &mut WindowContext) {
|
||||
pub fn focus(&mut self, cx: &mut WindowContext) {
|
||||
cx.focus(&self.focus_handle);
|
||||
}
|
||||
|
||||
pub fn set_placeholder(self, placeholder: &str, cx: &mut WindowContext) -> Self {
|
||||
self.view.update(cx, |text_view, cx| {
|
||||
text_view.set_placeholder(placeholder, cx)
|
||||
});
|
||||
self
|
||||
}
|
||||
|
||||
pub fn set_disabled(self, disabled: bool, cx: &mut WindowContext) -> Self {
|
||||
self.view
|
||||
.update(cx, |text_view, cx| text_view.set_disabled(disabled, cx));
|
||||
self
|
||||
}
|
||||
|
||||
pub fn set_text(self, text: &str, cx: &mut WindowContext) -> Self {
|
||||
self.view
|
||||
.update(cx, |text_view, cx| text_view.set_text(text, cx));
|
||||
self
|
||||
}
|
||||
|
||||
pub fn set_masked(self, masked: bool, cx: &mut WindowContext) -> Self {
|
||||
self.view
|
||||
.update(cx, |text_view, cx| text_view.set_masked(masked, cx));
|
||||
self
|
||||
}
|
||||
|
||||
fn handle_focus(&mut self, cx: &mut ViewContext<Self>) {
|
||||
cx.emit(TextEvent::Focus);
|
||||
self.blink_manager.update(cx, BlinkManager::enable);
|
||||
|
|
@ -64,156 +84,190 @@ impl TextField {
|
|||
}
|
||||
}
|
||||
|
||||
impl Disableable for TextField {
|
||||
fn disabled(mut self, disabled: bool) -> Self {
|
||||
self.disable = disabled;
|
||||
self
|
||||
}
|
||||
}
|
||||
impl RenderOnce for TextField {
|
||||
fn render(self, cx: &mut WindowContext) -> impl IntoElement {
|
||||
cx.focus(&self.focus_handle);
|
||||
let focus_handle = self.focus_handle.clone();
|
||||
let theme = cx.global::<Theme>();
|
||||
|
||||
let view = self.view.clone();
|
||||
let text_view = view.read(cx);
|
||||
|
||||
let focused = self.focus_handle.is_focused(cx);
|
||||
let disabled = text_view.disabled;
|
||||
|
||||
div()
|
||||
.track_focus(&self.focus_handle)
|
||||
.on_key_down(move |ev, cx| {
|
||||
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 = text_view.text.chars().collect::<Vec<char>>();
|
||||
let m = ev.keystroke.modifiers.secondary();
|
||||
.track_focus(&focus_handle)
|
||||
.when(!text_view.disabled, |this| {
|
||||
this.on_mouse_down(MouseButton::Left, move |_, cx| {
|
||||
cx.prevent_default();
|
||||
self.focus_handle.focus(cx)
|
||||
})
|
||||
})
|
||||
.when(!disabled, |this| {
|
||||
this.on_key_down(move |ev, cx| {
|
||||
if !focused {
|
||||
return;
|
||||
}
|
||||
|
||||
dbg!(&text_view.text, &text_view.selection);
|
||||
self.view.update(cx, |text_view, cx| {
|
||||
let prev = text_view.text.clone();
|
||||
cx.emit(TextEvent::KeyDown(ev.clone()));
|
||||
let keystroke = ev.keystroke.key.as_str();
|
||||
let chars = text_view.text.chars().collect::<Vec<char>>();
|
||||
let m = ev.keystroke.modifiers.secondary();
|
||||
|
||||
if m {
|
||||
match keystroke.as_str() {
|
||||
"a" => {
|
||||
text_view.selection = 0..chars.len();
|
||||
}
|
||||
"c" => {
|
||||
// if !text_view.masked {
|
||||
let selected_text =
|
||||
chars[text_view.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();
|
||||
text_view.text.replace_range(
|
||||
text_view.char_range_to_text_range(&text_view.text),
|
||||
text,
|
||||
);
|
||||
let i = text_view.selection.start + text.chars().count();
|
||||
text_view.selection = i..i;
|
||||
if m {
|
||||
match keystroke {
|
||||
"a" => {
|
||||
text_view.selection = 0..chars.len();
|
||||
}
|
||||
}
|
||||
"x" => {
|
||||
let selected_text =
|
||||
chars[text_view.selection.clone()].iter().collect();
|
||||
cx.write_to_clipboard(ClipboardItem::new(selected_text));
|
||||
text_view.text.replace_range(
|
||||
text_view.char_range_to_text_range(&text_view.text),
|
||||
"",
|
||||
);
|
||||
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();
|
||||
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 text_view.selection.start > 0 {
|
||||
let i = if text_view.selection.start == text_view.selection.end
|
||||
{
|
||||
text_view.selection.start - 1
|
||||
} else {
|
||||
text_view.selection.start
|
||||
};
|
||||
text_view.selection = i..i;
|
||||
"c" => {
|
||||
// if !text_view.masked {
|
||||
let selected_text =
|
||||
chars[text_view.selection.clone()].iter().collect();
|
||||
cx.write_to_clipboard(ClipboardItem::new(selected_text));
|
||||
// }
|
||||
}
|
||||
}
|
||||
"right" => {
|
||||
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 {
|
||||
text_view.selection.end
|
||||
};
|
||||
text_view.selection = i..i;
|
||||
"v" => {
|
||||
let clipboard = cx.read_from_clipboard();
|
||||
if let Some(clipboard) = clipboard {
|
||||
let text = clipboard.text();
|
||||
text_view.text.replace_range(
|
||||
text_view.char_range_to_text_range(&text_view.text),
|
||||
text,
|
||||
);
|
||||
let i = text_view.selection.start + text.chars().count();
|
||||
text_view.selection = i..i;
|
||||
}
|
||||
}
|
||||
}
|
||||
"backspace" => {
|
||||
if text_view.text.is_empty() && !ev.is_held {
|
||||
// cx.emit(TextEvent::Back);
|
||||
} else if text_view.selection.start == text_view.selection.end
|
||||
&& text_view.selection.start > 0
|
||||
{
|
||||
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>());
|
||||
text_view.selection = i..i;
|
||||
} else {
|
||||
"x" => {
|
||||
let selected_text =
|
||||
chars[text_view.selection.clone()].iter().collect();
|
||||
cx.write_to_clipboard(ClipboardItem::new(selected_text));
|
||||
text_view.text.replace_range(
|
||||
text_view.char_range_to_text_range(&text_view.text),
|
||||
"",
|
||||
);
|
||||
text_view.selection.end = text_view.selection.start;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
"enter" => {
|
||||
if ev.keystroke.modifiers.shift {
|
||||
text_view.text.insert(
|
||||
text_view.char_range_to_text_range(&text_view.text).start,
|
||||
'\n',
|
||||
);
|
||||
let i = text_view.selection.start + 1;
|
||||
text_view.selection = i..i;
|
||||
} else if ev.keystroke.modifiers.control {
|
||||
// On macOS, ctrl+a, ctrl+e are used for moving cursor to start/end of line
|
||||
match keystroke {
|
||||
"a" => {
|
||||
// Move cursor to first of line
|
||||
text_view.selection = 0..0;
|
||||
}
|
||||
"e" => {
|
||||
// Move cursor to end of line
|
||||
text_view.selection = chars.len()..chars.len();
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
_ => {}
|
||||
};
|
||||
}
|
||||
} else if !ev.keystroke.ime_key.clone().unwrap_or_default().is_empty() {
|
||||
let ime_key = &ev.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 {
|
||||
match keystroke {
|
||||
"left" => {
|
||||
if text_view.selection.start > 0 {
|
||||
let i = if text_view.selection.start
|
||||
== text_view.selection.end
|
||||
{
|
||||
text_view.selection.start - 1
|
||||
} else {
|
||||
text_view.selection.start
|
||||
};
|
||||
text_view.selection = i..i;
|
||||
}
|
||||
}
|
||||
"right" => {
|
||||
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 {
|
||||
text_view.selection.end
|
||||
};
|
||||
text_view.selection = i..i;
|
||||
}
|
||||
}
|
||||
"backspace" => {
|
||||
if text_view.text.is_empty() && !ev.is_held {
|
||||
// cx.emit(TextEvent::Back);
|
||||
} else if text_view.selection.start == text_view.selection.end
|
||||
&& text_view.selection.start > 0
|
||||
{
|
||||
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>());
|
||||
text_view.selection = i..i;
|
||||
} else {
|
||||
text_view.text.replace_range(
|
||||
text_view.char_range_to_text_range(&text_view.text),
|
||||
"",
|
||||
);
|
||||
text_view.selection.end = text_view.selection.start;
|
||||
}
|
||||
}
|
||||
"enter" => {
|
||||
if ev.keystroke.modifiers.shift {
|
||||
text_view.text.insert(
|
||||
text_view
|
||||
.char_range_to_text_range(&text_view.text)
|
||||
.start,
|
||||
'\n',
|
||||
);
|
||||
let i = text_view.selection.start + 1;
|
||||
text_view.selection = i..i;
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
if let Some(c) = keystroke.chars().next() {
|
||||
text_view.text.replace_range(
|
||||
text_view.char_range_to_text_range(&text_view.text),
|
||||
c.to_string().as_str(),
|
||||
);
|
||||
let i = text_view.selection.start + 1;
|
||||
text_view.selection = i..i;
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
if prev != text_view.text {
|
||||
cx.emit(TextEvent::Input {
|
||||
text: text_view.text.clone(),
|
||||
});
|
||||
dbg!(&text_view.text, &text_view.selection);
|
||||
}
|
||||
cx.notify();
|
||||
});
|
||||
if prev != text_view.text {
|
||||
cx.emit(TextEvent::Input {
|
||||
text: text_view.text.clone(),
|
||||
});
|
||||
}
|
||||
cx.notify();
|
||||
});
|
||||
})
|
||||
})
|
||||
.border_color(if self.focus_handle.is_focused(cx) {
|
||||
theme.ring
|
||||
} else {
|
||||
theme.input
|
||||
.when(!disabled, |this| {
|
||||
this.when(focused, |this| this.border_color(theme.ring))
|
||||
})
|
||||
.border_color(theme.input)
|
||||
.border_1()
|
||||
.rounded_sm()
|
||||
.py_1p5()
|
||||
.py_1()
|
||||
.px_3()
|
||||
.h_9()
|
||||
.shadow_sm()
|
||||
.min_w_20()
|
||||
.bg(if self.disable {
|
||||
theme.background.opacity(0.5)
|
||||
.bg(if disabled {
|
||||
theme.muted
|
||||
} else {
|
||||
theme.background
|
||||
theme.transparent
|
||||
})
|
||||
.child(view)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,21 +15,17 @@ pub struct TextView {
|
|||
pub placeholder: String,
|
||||
pub word_click: (usize, u16),
|
||||
pub selection: Range<usize>,
|
||||
pub disable: bool,
|
||||
pub disabled: bool,
|
||||
pub blink_manager: Model<BlinkManager>,
|
||||
pub cursor: CursorLayout,
|
||||
pub masked: bool,
|
||||
pub focused: bool,
|
||||
}
|
||||
|
||||
impl EventEmitter<TextEvent> for TextView {}
|
||||
|
||||
impl TextView {
|
||||
pub fn init(
|
||||
cx: &mut WindowContext,
|
||||
focus_handle: &FocusHandle,
|
||||
placeholder: &str,
|
||||
disable: bool,
|
||||
) -> View<Self> {
|
||||
pub fn init(cx: &mut WindowContext, focus_handle: &FocusHandle) -> View<Self> {
|
||||
let blink_manager = cx.new_model(|cx| BlinkManager::new(CURSOR_BLINK_INTERVAL, cx));
|
||||
|
||||
let theme = cx.global::<Theme>();
|
||||
|
|
@ -44,29 +40,24 @@ impl TextView {
|
|||
|
||||
let m = Self {
|
||||
text: String::new(),
|
||||
placeholder: placeholder.to_string(),
|
||||
placeholder: "".to_string(),
|
||||
word_click: (0, 0),
|
||||
selection: 0..0,
|
||||
blink_manager,
|
||||
cursor,
|
||||
disable,
|
||||
disabled: false,
|
||||
masked: false,
|
||||
focused: false,
|
||||
};
|
||||
|
||||
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);
|
||||
},
|
||||
)
|
||||
cx.on_blur(focus_handle, |view: &mut TextView, cx| {
|
||||
view.blur(cx);
|
||||
})
|
||||
.detach();
|
||||
|
||||
cx.on_focus(focus_handle, |view, cx| {
|
||||
view.blink_manager.update(cx, |bm, cx| {
|
||||
bm.blink_cursor(0, cx);
|
||||
});
|
||||
view.focus(cx);
|
||||
})
|
||||
.detach();
|
||||
m
|
||||
|
|
@ -99,6 +90,22 @@ impl TextView {
|
|||
cx.notify();
|
||||
}
|
||||
|
||||
pub fn blur(&mut self, cx: &mut ViewContext<Self>) {
|
||||
self.focused = false;
|
||||
self.blink_manager.update(cx, BlinkManager::disable);
|
||||
cx.notify();
|
||||
cx.emit(TextEvent::Blur);
|
||||
}
|
||||
|
||||
pub fn focus(&mut self, cx: &mut ViewContext<Self>) {
|
||||
self.focused = true;
|
||||
self.blink_manager.update(cx, |bm, cx| {
|
||||
bm.blink_cursor(0, cx);
|
||||
});
|
||||
cx.notify();
|
||||
cx.emit(TextEvent::Focus);
|
||||
}
|
||||
|
||||
pub fn word_ranges(&self) -> Vec<Range<usize>> {
|
||||
let mut words = Vec::new();
|
||||
let mut last_was_boundary = true;
|
||||
|
|
@ -150,6 +157,21 @@ impl TextView {
|
|||
});
|
||||
}
|
||||
|
||||
pub fn set_masked(&mut self, masked: bool, cx: &mut ViewContext<Self>) {
|
||||
self.masked = masked;
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
pub fn set_placeholder(&mut self, placeholder: impl ToString, cx: &mut ViewContext<Self>) {
|
||||
self.placeholder = placeholder.to_string();
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
pub fn set_disabled(&mut self, disabled: bool, cx: &mut ViewContext<Self>) {
|
||||
self.disabled = disabled;
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
fn paint_cursors(&self, layout: &TextLayout, cx: &mut WindowContext) {
|
||||
let mut cursor = self.cursor.clone();
|
||||
dbg!("--------- paint_cursors", &cursor);
|
||||
|
|
@ -217,8 +239,9 @@ impl Element for TextView {
|
|||
impl Render for TextView {
|
||||
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
|
||||
let theme = cx.global::<Theme>();
|
||||
|
||||
let view = cx.view().clone();
|
||||
let mut text = self.text.clone();
|
||||
dbg!("textView render", &text);
|
||||
|
||||
let mut style = TextStyle {
|
||||
color: theme.foreground,
|
||||
|
|
@ -241,8 +264,11 @@ impl Render for TextView {
|
|||
highlights = vec![];
|
||||
}
|
||||
|
||||
if !self.focused {
|
||||
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.update(cx, |text_view, cx| {
|
||||
|
|
|
|||
|
|
@ -162,6 +162,7 @@ impl Colors {
|
|||
|
||||
#[derive(Debug)]
|
||||
pub struct Theme {
|
||||
pub transparent: Hsla,
|
||||
pub background: Hsla,
|
||||
pub foreground: Hsla,
|
||||
pub card: Hsla,
|
||||
|
|
@ -189,6 +190,7 @@ impl Global for Theme {}
|
|||
impl From<Colors> for Theme {
|
||||
fn from(colors: Colors) -> Self {
|
||||
Theme {
|
||||
transparent: Hsla::transparent_black(),
|
||||
background: colors.background,
|
||||
foreground: colors.foreground,
|
||||
card: colors.card,
|
||||
|
|
@ -220,7 +222,7 @@ pub enum ThemeMode {
|
|||
|
||||
impl Theme {
|
||||
fn new() -> Self {
|
||||
Self::from(Colors::light())
|
||||
Self::from(Colors::dark())
|
||||
}
|
||||
|
||||
pub fn init(cx: &mut AppContext) {
|
||||
|
|
|
|||
Loading…
Reference in a new issue