Add invert method to Hsla.
This commit is contained in:
parent
dad6f4c431
commit
3a745d2abe
5 changed files with 153 additions and 77 deletions
|
|
@ -1,6 +1,6 @@
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
use gpui::ModelContext;
|
use gpui::{ModelContext, Timer};
|
||||||
|
|
||||||
pub struct BlinkManager {
|
pub struct BlinkManager {
|
||||||
blink_interval: Duration,
|
blink_interval: Duration,
|
||||||
|
|
@ -21,6 +21,30 @@ impl BlinkManager {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn pause_blinking(&mut self, cx: &mut ModelContext<Self>) {
|
||||||
|
self.show_cursor(cx);
|
||||||
|
|
||||||
|
let epoch = self.next_blink_epoch();
|
||||||
|
let interval = self.blink_interval;
|
||||||
|
cx.spawn(|this, mut cx| async move {
|
||||||
|
Timer::after(interval).await;
|
||||||
|
this.update(&mut cx, |this, cx| this.resume_cursor_blinking(epoch, cx))
|
||||||
|
})
|
||||||
|
.detach();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn next_blink_epoch(&mut self) -> usize {
|
||||||
|
self.blink_epoch += 1;
|
||||||
|
self.blink_epoch
|
||||||
|
}
|
||||||
|
|
||||||
|
fn resume_cursor_blinking(&mut self, epoch: usize, cx: &mut ModelContext<Self>) {
|
||||||
|
if epoch == self.blink_epoch {
|
||||||
|
self.blinking_paused = false;
|
||||||
|
self.blink_cursor(epoch, cx);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
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)
|
self.enabled && (!self.blinking_paused || self.visible)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,6 @@
|
||||||
use gpui::{outline, px, Bounds, Hsla, Pixels, ShapedLine, Size, ViewContext, WindowContext};
|
use gpui::{
|
||||||
|
fill, outline, px, size, Bounds, Hsla, Pixels, ShapedLine, Size, ViewContext, WindowContext,
|
||||||
|
};
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct CursorLayout {
|
pub struct CursorLayout {
|
||||||
|
|
@ -28,12 +30,23 @@ impl CursorLayout {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn bounds(&self, origin: gpui::Point<Pixels>) -> Bounds<Pixels> {
|
fn bounds(&self, origin: gpui::Point<Pixels>) -> Bounds<Pixels> {
|
||||||
|
// Return a bar sharp cursor
|
||||||
Bounds {
|
Bounds {
|
||||||
origin: self.origin + origin,
|
origin: self.origin + origin,
|
||||||
size: Size {
|
size: size(px(2.0), self.line_height),
|
||||||
width: px(2.0),
|
}
|
||||||
height: self.line_height,
|
}
|
||||||
},
|
|
||||||
|
pub fn layout(&mut self, origin: gpui::Point<Pixels>, cx: &mut WindowContext) {
|
||||||
|
let bounds = self.bounds(origin);
|
||||||
|
let cursor = fill(bounds, self.color);
|
||||||
|
|
||||||
|
cx.paint_quad(cursor);
|
||||||
|
|
||||||
|
if let Some(block_text) = &self.block_text {
|
||||||
|
block_text
|
||||||
|
.paint(self.origin + origin, self.line_height, cx)
|
||||||
|
.unwrap()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,15 +2,10 @@ mod blink_manager;
|
||||||
mod cursor_layout;
|
mod cursor_layout;
|
||||||
mod text_view;
|
mod text_view;
|
||||||
|
|
||||||
use crate::{
|
use crate::theme::Theme;
|
||||||
disableable::Disableable,
|
|
||||||
theme::{Colorize as _, Theme},
|
|
||||||
};
|
|
||||||
use blink_manager::BlinkManager;
|
|
||||||
use gpui::{
|
use gpui::{
|
||||||
div, prelude::FluentBuilder as _, ClipboardItem, Context, Entity, EventEmitter, FocusHandle,
|
div, prelude::FluentBuilder as _, ClipboardItem, EventEmitter, FocusHandle, InteractiveElement,
|
||||||
InteractiveElement, IntoElement, KeyDownEvent, Model, MouseButton, ParentElement, RenderOnce,
|
IntoElement, KeyDownEvent, MouseButton, ParentElement, RenderOnce, Styled, View, WindowContext,
|
||||||
Styled, View, ViewContext, WindowContext,
|
|
||||||
};
|
};
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
use text_view::TextView;
|
use text_view::TextView;
|
||||||
|
|
@ -20,7 +15,6 @@ const CURSOR_BLINK_INTERVAL: Duration = Duration::from_millis(500);
|
||||||
#[derive(Clone, IntoElement)]
|
#[derive(Clone, IntoElement)]
|
||||||
pub struct TextField {
|
pub struct TextField {
|
||||||
focus_handle: FocusHandle,
|
focus_handle: FocusHandle,
|
||||||
blink_manager: Model<BlinkManager>,
|
|
||||||
pub view: View<TextView>,
|
pub view: View<TextView>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -29,13 +23,7 @@ impl TextField {
|
||||||
let focus_handle = cx.focus_handle();
|
let focus_handle = cx.focus_handle();
|
||||||
let view = TextView::init(cx, &focus_handle);
|
let view = TextView::init(cx, &focus_handle);
|
||||||
|
|
||||||
let blink_manager = cx.new_model(|cx| BlinkManager::new(CURSOR_BLINK_INTERVAL, cx));
|
Self { focus_handle, view }
|
||||||
|
|
||||||
Self {
|
|
||||||
focus_handle,
|
|
||||||
view,
|
|
||||||
blink_manager,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn focus(&mut self, cx: &mut WindowContext) {
|
pub fn focus(&mut self, cx: &mut WindowContext) {
|
||||||
|
|
@ -66,22 +54,6 @@ impl TextField {
|
||||||
.update(cx, |text_view, cx| text_view.set_masked(masked, cx));
|
.update(cx, |text_view, cx| text_view.set_masked(masked, cx));
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
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 RenderOnce for TextField {
|
impl RenderOnce for TextField {
|
||||||
|
|
@ -105,10 +77,6 @@ impl RenderOnce for TextField {
|
||||||
})
|
})
|
||||||
.when(!disabled, |this| {
|
.when(!disabled, |this| {
|
||||||
this.on_key_down(move |ev, cx| {
|
this.on_key_down(move |ev, cx| {
|
||||||
if !focused {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
self.view.update(cx, |text_view, cx| {
|
self.view.update(cx, |text_view, cx| {
|
||||||
let prev = text_view.text.clone();
|
let prev = text_view.text.clone();
|
||||||
cx.emit(TextEvent::KeyDown(ev.clone()));
|
cx.emit(TextEvent::KeyDown(ev.clone()));
|
||||||
|
|
|
||||||
|
|
@ -4,20 +4,27 @@ use super::{
|
||||||
blink_manager::BlinkManager, cursor_layout::CursorLayout, TextEvent, CURSOR_BLINK_INTERVAL,
|
blink_manager::BlinkManager, cursor_layout::CursorLayout, TextEvent, CURSOR_BLINK_INTERVAL,
|
||||||
};
|
};
|
||||||
use crate::theme::{Colorize as _, Theme};
|
use crate::theme::{Colorize as _, Theme};
|
||||||
|
use catppuccin::Hsl;
|
||||||
use gpui::{
|
use gpui::{
|
||||||
px, relative, Context, Element, EventEmitter, FocusHandle, HighlightStyle, InteractiveText,
|
px, relative, ContentMask, Context, Element, EventEmitter, FocusHandle, HighlightStyle, Hsla,
|
||||||
IntoElement, Model, Render, Style, StyledText, TextStyle, View, ViewContext, VisualContext,
|
InteractiveText, IntoElement, Model, Point, Render, Style, StyledText, TextStyle,
|
||||||
WindowContext,
|
TextStyleRefinement, View, ViewContext, VisualContext, WindowContext,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct TextFieldStyle {
|
||||||
|
pub background: Hsla,
|
||||||
|
pub text: TextStyle,
|
||||||
|
}
|
||||||
|
|
||||||
pub struct TextView {
|
pub struct TextView {
|
||||||
pub text: String,
|
pub text: String,
|
||||||
|
pub style: TextFieldStyle,
|
||||||
pub placeholder: String,
|
pub placeholder: String,
|
||||||
pub word_click: (usize, u16),
|
pub word_click: (usize, u16),
|
||||||
pub selection: Range<usize>,
|
pub selection: Range<usize>,
|
||||||
pub disabled: bool,
|
pub disabled: bool,
|
||||||
pub blink_manager: Model<BlinkManager>,
|
pub blink_manager: Model<BlinkManager>,
|
||||||
pub cursor: CursorLayout,
|
|
||||||
pub masked: bool,
|
pub masked: bool,
|
||||||
pub focused: bool,
|
pub focused: bool,
|
||||||
}
|
}
|
||||||
|
|
@ -30,21 +37,23 @@ impl TextView {
|
||||||
|
|
||||||
let theme = cx.global::<Theme>();
|
let theme = cx.global::<Theme>();
|
||||||
|
|
||||||
let cursor = CursorLayout::new(
|
let line_height = px(20.0);
|
||||||
gpui::Point::new(gpui::px(0.0), gpui::px(0.0)),
|
let style = TextFieldStyle {
|
||||||
px(2.0),
|
background: theme.transparent,
|
||||||
px(20.0),
|
text: TextStyle {
|
||||||
theme.ring,
|
color: theme.foreground,
|
||||||
None,
|
line_height: line_height.into(),
|
||||||
);
|
..Default::default()
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
let m = Self {
|
let m = Self {
|
||||||
text: String::new(),
|
text: String::new(),
|
||||||
|
style,
|
||||||
placeholder: "".to_string(),
|
placeholder: "".to_string(),
|
||||||
word_click: (0, 0),
|
word_click: (0, 0),
|
||||||
selection: 0..0,
|
selection: 0..0,
|
||||||
blink_manager,
|
blink_manager: blink_manager.clone(),
|
||||||
cursor,
|
|
||||||
disabled: false,
|
disabled: false,
|
||||||
masked: false,
|
masked: false,
|
||||||
focused: false,
|
focused: false,
|
||||||
|
|
@ -60,6 +69,22 @@ impl TextView {
|
||||||
view.focus(cx);
|
view.focus(cx);
|
||||||
})
|
})
|
||||||
.detach();
|
.detach();
|
||||||
|
|
||||||
|
cx.observe(&blink_manager, |_, _, cx| cx.notify()).detach();
|
||||||
|
|
||||||
|
cx.observe_window_activation(|view, cx| {
|
||||||
|
let active = cx.is_window_active();
|
||||||
|
view.blink_manager.update(cx, |blink_manager, cx| {
|
||||||
|
if active {
|
||||||
|
blink_manager.enable(cx);
|
||||||
|
} else {
|
||||||
|
blink_manager.show_cursor(cx);
|
||||||
|
blink_manager.disable(cx);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.detach();
|
||||||
|
|
||||||
m
|
m
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -134,6 +159,7 @@ impl TextView {
|
||||||
words
|
words
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Converts a character range to a text range (in bytes)
|
||||||
pub fn char_range_to_text_range(&self, text: &str) -> Range<usize> {
|
pub fn char_range_to_text_range(&self, text: &str) -> Range<usize> {
|
||||||
let start = text
|
let start = text
|
||||||
.chars()
|
.chars()
|
||||||
|
|
@ -173,10 +199,23 @@ impl TextView {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn paint_cursors(&self, layout: &TextLayout, cx: &mut WindowContext) {
|
fn paint_cursors(&self, layout: &TextLayout, cx: &mut WindowContext) {
|
||||||
let mut cursor = self.cursor.clone();
|
let mut cursor = layout.visible_cursor.clone();
|
||||||
dbg!("--------- paint_cursors", &cursor);
|
|
||||||
cursor.paint(layout.content_origin, cx);
|
cursor.paint(layout.content_origin, cx);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn show_cursor(&self, cx: &mut WindowContext) -> bool {
|
||||||
|
self.blink_manager.read(cx).visible() && self.focused
|
||||||
|
}
|
||||||
|
|
||||||
|
fn layout_visible_cursors(&self, cx: &mut WindowContext) -> CursorLayout {
|
||||||
|
let theme = cx.global::<Theme>();
|
||||||
|
let selection = &self.selection;
|
||||||
|
|
||||||
|
let x = px(selection.end as f32);
|
||||||
|
let y = px(0.);
|
||||||
|
|
||||||
|
CursorLayout::new(Point::new(x, y), px(0.), px(20.0), theme.ring, None)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl IntoElement for TextView {
|
impl IntoElement for TextView {
|
||||||
|
|
@ -189,6 +228,7 @@ impl IntoElement for TextView {
|
||||||
|
|
||||||
pub struct TextLayout {
|
pub struct TextLayout {
|
||||||
content_origin: gpui::Point<gpui::Pixels>,
|
content_origin: gpui::Point<gpui::Pixels>,
|
||||||
|
visible_cursor: CursorLayout,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Element for TextView {
|
impl Element for TextView {
|
||||||
|
|
@ -202,37 +242,62 @@ impl Element for TextView {
|
||||||
|
|
||||||
fn request_layout(
|
fn request_layout(
|
||||||
&mut self,
|
&mut self,
|
||||||
id: Option<&gpui::GlobalElementId>,
|
_id: Option<&gpui::GlobalElementId>,
|
||||||
cx: &mut WindowContext,
|
cx: &mut WindowContext,
|
||||||
) -> (gpui::LayoutId, Self::RequestLayoutState) {
|
) -> (gpui::LayoutId, Self::RequestLayoutState) {
|
||||||
let mut style = Style::default();
|
let mut style = Style::default();
|
||||||
|
let rem_size = cx.rem_size();
|
||||||
|
|
||||||
style.size.width = relative(1.).into();
|
style.size.width = relative(1.).into();
|
||||||
style.size.height = relative(24.).into();
|
style.size.height = self.style.text.line_height_in_pixels(rem_size).into();
|
||||||
dbg!("--------- request_layout", &style);
|
|
||||||
(cx.request_layout(style, None), ())
|
(cx.request_layout(style, None), ())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn prepaint(
|
fn prepaint(
|
||||||
&mut self,
|
&mut self,
|
||||||
id: Option<&gpui::GlobalElementId>,
|
_id: Option<&gpui::GlobalElementId>,
|
||||||
bounds: gpui::Bounds<gpui::Pixels>,
|
bounds: gpui::Bounds<gpui::Pixels>,
|
||||||
request_layout: &mut Self::RequestLayoutState,
|
_request_layout: &mut Self::RequestLayoutState,
|
||||||
cx: &mut WindowContext,
|
cx: &mut WindowContext,
|
||||||
) -> Self::PrepaintState {
|
) -> Self::PrepaintState {
|
||||||
TextLayout {
|
let text_style = TextStyleRefinement {
|
||||||
content_origin: bounds.origin,
|
font_size: Some(self.style.text.font_size),
|
||||||
}
|
line_height: Some(self.style.text.line_height),
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
|
||||||
|
cx.with_text_style(Some(text_style), |cx| {
|
||||||
|
cx.with_content_mask(Some(ContentMask { bounds }), |cx| {
|
||||||
|
let cursor = self.layout_visible_cursors(cx);
|
||||||
|
|
||||||
|
TextLayout {
|
||||||
|
content_origin: bounds.origin,
|
||||||
|
visible_cursor: cursor,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn paint(
|
fn paint(
|
||||||
&mut self,
|
&mut self,
|
||||||
id: Option<&gpui::GlobalElementId>,
|
_id: Option<&gpui::GlobalElementId>,
|
||||||
bounds: gpui::Bounds<gpui::Pixels>,
|
bounds: gpui::Bounds<gpui::Pixels>,
|
||||||
request_layout: &mut Self::RequestLayoutState,
|
_request_layout: &mut Self::RequestLayoutState,
|
||||||
layout: &mut Self::PrepaintState,
|
layout: &mut Self::PrepaintState,
|
||||||
cx: &mut WindowContext,
|
cx: &mut WindowContext,
|
||||||
) {
|
) {
|
||||||
self.paint_cursors(layout, cx);
|
let text_style = TextStyleRefinement {
|
||||||
|
font_size: Some(self.style.text.font_size),
|
||||||
|
line_height: Some(self.style.text.line_height),
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
|
||||||
|
cx.with_text_style(Some(text_style), |cx| {
|
||||||
|
cx.with_content_mask(Some(ContentMask { bounds }), |cx| {
|
||||||
|
self.paint_cursors(layout, cx);
|
||||||
|
})
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -243,32 +308,27 @@ impl Render for TextView {
|
||||||
let view = cx.view().clone();
|
let view = cx.view().clone();
|
||||||
let mut text = self.text.clone();
|
let mut text = self.text.clone();
|
||||||
|
|
||||||
let mut style = TextStyle {
|
let mut style = self.style.text.clone();
|
||||||
color: theme.foreground,
|
|
||||||
..Default::default()
|
|
||||||
};
|
|
||||||
|
|
||||||
if self.masked {
|
if self.masked {
|
||||||
text = "•".repeat(text.len());
|
text = "•".repeat(text.len());
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut selection_style = HighlightStyle::default();
|
let mut selection_style = HighlightStyle::default();
|
||||||
let color = theme.foreground.opacity(0.8);
|
selection_style.background_color = Some(theme.ring);
|
||||||
selection_style.background_color = Some(color);
|
selection_style.color = Some(theme.ring.invert());
|
||||||
|
|
||||||
let mut highlights = vec![(self.char_range_to_text_range(&text), selection_style)];
|
let mut highlights = vec![(self.char_range_to_text_range(&text), selection_style)];
|
||||||
|
|
||||||
if text.is_empty() {
|
if text.is_empty() {
|
||||||
text = self.placeholder.to_string();
|
text = self.placeholder.to_string();
|
||||||
style.color = theme.muted_foreground;
|
style.color = theme.muted_foreground;
|
||||||
highlights = vec![];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if !self.focused {
|
if !self.focused {
|
||||||
highlights = vec![];
|
highlights = vec![];
|
||||||
}
|
}
|
||||||
|
|
||||||
let styled_text = StyledText::new(text + " ").with_highlights(&style, highlights);
|
let styled_text = StyledText::new(text).with_highlights(&style, highlights);
|
||||||
|
|
||||||
InteractiveText::new("text", styled_text).on_click(self.word_ranges(), move |ev, cx| {
|
InteractiveText::new("text", styled_text).on_click(self.word_ranges(), move |ev, cx| {
|
||||||
view.update(cx, |text_view, cx| {
|
view.update(cx, |text_view, cx| {
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,7 @@ fn hsl(h: f32, s: f32, l: f32) -> Hsla {
|
||||||
pub trait Colorize {
|
pub trait Colorize {
|
||||||
fn opacity(&self, opacity: f32) -> Hsla;
|
fn opacity(&self, opacity: f32) -> Hsla;
|
||||||
fn divide(&self, divisor: f32) -> Hsla;
|
fn divide(&self, divisor: f32) -> Hsla;
|
||||||
|
fn invert(&self) -> Hsla;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Colorize for Hsla {
|
impl Colorize for Hsla {
|
||||||
|
|
@ -35,6 +36,16 @@ impl Colorize for Hsla {
|
||||||
..*self
|
..*self
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Return inverted color
|
||||||
|
fn invert(&self) -> Hsla {
|
||||||
|
Hsla {
|
||||||
|
h: (self.h + 1.8) % 3.6,
|
||||||
|
s: 1.0 - self.s,
|
||||||
|
l: 1.0 - self.l,
|
||||||
|
a: self.a,
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// @layer base {
|
// @layer base {
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue