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 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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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()));
|
||||
|
|
|
|||
|
|
@ -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| {
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
Loading…
Reference in a new issue