Add invert method to Hsla.

This commit is contained in:
Jason Lee 2024-06-24 19:01:11 +08:00
parent dad6f4c431
commit 3a745d2abe
5 changed files with 153 additions and 77 deletions

View file

@ -1,6 +1,6 @@
use std::time::Duration;
use gpui::ModelContext;
use gpui::{ModelContext, Timer};
pub struct BlinkManager {
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 {
self.enabled && (!self.blinking_paused || self.visible)
}

View file

@ -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)]
pub struct CursorLayout {
@ -28,12 +30,23 @@ impl CursorLayout {
}
fn bounds(&self, origin: gpui::Point<Pixels>) -> Bounds<Pixels> {
// Return a bar sharp cursor
Bounds {
origin: self.origin + origin,
size: Size {
width: px(2.0),
height: self.line_height,
},
size: size(px(2.0), 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()
}
}

View file

@ -2,15 +2,10 @@ mod blink_manager;
mod cursor_layout;
mod text_view;
use crate::{
disableable::Disableable,
theme::{Colorize as _, Theme},
};
use blink_manager::BlinkManager;
use crate::theme::Theme;
use gpui::{
div, prelude::FluentBuilder as _, ClipboardItem, Context, Entity, EventEmitter, FocusHandle,
InteractiveElement, IntoElement, KeyDownEvent, Model, MouseButton, ParentElement, RenderOnce,
Styled, View, ViewContext, WindowContext,
div, prelude::FluentBuilder as _, ClipboardItem, EventEmitter, FocusHandle, InteractiveElement,
IntoElement, KeyDownEvent, MouseButton, ParentElement, RenderOnce, Styled, View, WindowContext,
};
use std::time::Duration;
use text_view::TextView;
@ -20,7 +15,6 @@ const CURSOR_BLINK_INTERVAL: Duration = Duration::from_millis(500);
#[derive(Clone, IntoElement)]
pub struct TextField {
focus_handle: FocusHandle,
blink_manager: Model<BlinkManager>,
pub view: View<TextView>,
}
@ -29,13 +23,7 @@ impl TextField {
let focus_handle = 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,
blink_manager,
}
Self { focus_handle, view }
}
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));
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 {
@ -105,10 +77,6 @@ impl RenderOnce for TextField {
})
.when(!disabled, |this| {
this.on_key_down(move |ev, cx| {
if !focused {
return;
}
self.view.update(cx, |text_view, cx| {
let prev = text_view.text.clone();
cx.emit(TextEvent::KeyDown(ev.clone()));

View file

@ -4,20 +4,27 @@ use super::{
blink_manager::BlinkManager, cursor_layout::CursorLayout, TextEvent, CURSOR_BLINK_INTERVAL,
};
use crate::theme::{Colorize as _, Theme};
use catppuccin::Hsl;
use gpui::{
px, relative, Context, Element, EventEmitter, FocusHandle, HighlightStyle, InteractiveText,
IntoElement, Model, Render, Style, StyledText, TextStyle, View, ViewContext, VisualContext,
WindowContext,
px, relative, ContentMask, Context, Element, EventEmitter, FocusHandle, HighlightStyle, Hsla,
InteractiveText, IntoElement, Model, Point, Render, Style, StyledText, TextStyle,
TextStyleRefinement, View, ViewContext, VisualContext, WindowContext,
};
#[derive(Clone)]
pub struct TextFieldStyle {
pub background: Hsla,
pub text: TextStyle,
}
pub struct TextView {
pub text: String,
pub style: TextFieldStyle,
pub placeholder: String,
pub word_click: (usize, u16),
pub selection: Range<usize>,
pub disabled: bool,
pub blink_manager: Model<BlinkManager>,
pub cursor: CursorLayout,
pub masked: bool,
pub focused: bool,
}
@ -30,21 +37,23 @@ impl TextView {
let theme = cx.global::<Theme>();
let cursor = CursorLayout::new(
gpui::Point::new(gpui::px(0.0), gpui::px(0.0)),
px(2.0),
px(20.0),
theme.ring,
None,
);
let line_height = px(20.0);
let style = TextFieldStyle {
background: theme.transparent,
text: TextStyle {
color: theme.foreground,
line_height: line_height.into(),
..Default::default()
},
};
let m = Self {
text: String::new(),
style,
placeholder: "".to_string(),
word_click: (0, 0),
selection: 0..0,
blink_manager,
cursor,
blink_manager: blink_manager.clone(),
disabled: false,
masked: false,
focused: false,
@ -60,6 +69,22 @@ impl TextView {
view.focus(cx);
})
.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
});
@ -134,6 +159,7 @@ impl TextView {
words
}
/// Converts a character range to a text range (in bytes)
pub fn char_range_to_text_range(&self, text: &str) -> Range<usize> {
let start = text
.chars()
@ -173,10 +199,23 @@ impl TextView {
}
fn paint_cursors(&self, layout: &TextLayout, cx: &mut WindowContext) {
let mut cursor = self.cursor.clone();
dbg!("--------- paint_cursors", &cursor);
let mut cursor = layout.visible_cursor.clone();
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 {
@ -189,6 +228,7 @@ impl IntoElement for TextView {
pub struct TextLayout {
content_origin: gpui::Point<gpui::Pixels>,
visible_cursor: CursorLayout,
}
impl Element for TextView {
@ -202,37 +242,62 @@ impl Element for TextView {
fn request_layout(
&mut self,
id: Option<&gpui::GlobalElementId>,
_id: Option<&gpui::GlobalElementId>,
cx: &mut WindowContext,
) -> (gpui::LayoutId, Self::RequestLayoutState) {
let mut style = Style::default();
let rem_size = cx.rem_size();
style.size.width = relative(1.).into();
style.size.height = relative(24.).into();
dbg!("--------- request_layout", &style);
style.size.height = self.style.text.line_height_in_pixels(rem_size).into();
(cx.request_layout(style, None), ())
}
fn prepaint(
&mut self,
id: Option<&gpui::GlobalElementId>,
_id: Option<&gpui::GlobalElementId>,
bounds: gpui::Bounds<gpui::Pixels>,
request_layout: &mut Self::RequestLayoutState,
_request_layout: &mut Self::RequestLayoutState,
cx: &mut WindowContext,
) -> Self::PrepaintState {
TextLayout {
content_origin: bounds.origin,
}
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| {
let cursor = self.layout_visible_cursors(cx);
TextLayout {
content_origin: bounds.origin,
visible_cursor: cursor,
}
})
})
}
fn paint(
&mut self,
id: Option<&gpui::GlobalElementId>,
_id: Option<&gpui::GlobalElementId>,
bounds: gpui::Bounds<gpui::Pixels>,
request_layout: &mut Self::RequestLayoutState,
_request_layout: &mut Self::RequestLayoutState,
layout: &mut Self::PrepaintState,
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 mut text = self.text.clone();
let mut style = TextStyle {
color: theme.foreground,
..Default::default()
};
let mut style = self.style.text.clone();
if self.masked {
text = "".repeat(text.len());
}
let mut selection_style = HighlightStyle::default();
let color = theme.foreground.opacity(0.8);
selection_style.background_color = Some(color);
selection_style.background_color = Some(theme.ring);
selection_style.color = Some(theme.ring.invert());
let mut highlights = vec![(self.char_range_to_text_range(&text), selection_style)];
if text.is_empty() {
text = self.placeholder.to_string();
style.color = theme.muted_foreground;
highlights = vec![];
}
if !self.focused {
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| {
view.update(cx, |text_view, cx| {

View file

@ -13,6 +13,7 @@ fn hsl(h: f32, s: f32, l: f32) -> Hsla {
pub trait Colorize {
fn opacity(&self, opacity: f32) -> Hsla;
fn divide(&self, divisor: f32) -> Hsla;
fn invert(&self) -> Hsla;
}
impl Colorize for Hsla {
@ -35,6 +36,16 @@ impl Colorize for Hsla {
..*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 {