Initial public release of Warp.
Repo-Sync-Origin: warpdotdev/warp-internal@12af1d983b
This commit is contained in:
@@ -0,0 +1,202 @@
|
||||
use pathfinder_color::ColorU;
|
||||
use pathfinder_geometry::vector::Vector2F;
|
||||
use warp_core::ui::{appearance::Appearance, Icon};
|
||||
use warpui::{
|
||||
elements::{
|
||||
Align, ConstrainedBox, Container, CornerRadius, CrossAxisAlignment, DropShadow, Flex,
|
||||
MainAxisAlignment, ParentElement as _, Radius, Shrinkable,
|
||||
},
|
||||
fonts::Weight,
|
||||
ui_components::components::{BorderStyle, Coords, UiComponent as _, UiComponentStyles},
|
||||
AppContext, Element, Entity, FocusContext, SingletonEntity as _, TypedActionView, View,
|
||||
ViewContext, ViewHandle,
|
||||
};
|
||||
|
||||
use crate::editor::{EditorOptions, EditorView, Event as EditorEvent, TextOptions};
|
||||
|
||||
const PROMPT_INPUT_HEIGHT: f32 = 56.;
|
||||
const ICON_MARGIN_LEFT: f32 = 12.;
|
||||
const ICON_MARGIN_RIGHT: f32 = 6.;
|
||||
|
||||
pub struct GlowingEditor {
|
||||
editor: ViewHandle<EditorView>,
|
||||
/// A closure that returns if the current editor content are valid and can be submitted.
|
||||
validator: Box<dyn Fn(&str) -> bool>,
|
||||
/// Whether or not the last submission attempt failed validation.
|
||||
has_error: bool,
|
||||
}
|
||||
|
||||
impl GlowingEditor {
|
||||
pub fn new(placeholder: impl Into<String>, ctx: &mut ViewContext<Self>) -> Self {
|
||||
let appearance = Appearance::as_ref(ctx);
|
||||
let font_family = appearance.ui_font_family();
|
||||
let font_size = appearance.ui_font_size() + 2.;
|
||||
|
||||
let editor = ctx.add_typed_action_view(|ctx| {
|
||||
let options = EditorOptions {
|
||||
soft_wrap: true,
|
||||
text: TextOptions {
|
||||
font_size_override: Some(font_size),
|
||||
font_family_override: Some(font_family),
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
};
|
||||
let mut editor = EditorView::new(options, ctx);
|
||||
editor.set_placeholder_text(placeholder, ctx);
|
||||
editor
|
||||
});
|
||||
|
||||
ctx.subscribe_to_view(&editor, move |me, _, event, ctx| {
|
||||
me.handle_editor_event(event, ctx);
|
||||
});
|
||||
|
||||
Self {
|
||||
editor,
|
||||
validator: Box::new(|_| true),
|
||||
has_error: false,
|
||||
}
|
||||
}
|
||||
|
||||
/// Validates the input contents using the provided `validator` whenever a submit action is
|
||||
/// attempted.
|
||||
#[expect(dead_code, reason = "Nothing needs validation currently.")]
|
||||
pub fn with_validator<F: Fn(&str) -> bool + 'static>(mut self, validator: F) -> Self {
|
||||
self.validator = Box::new(validator);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn clear_buffer(&mut self, ctx: &mut ViewContext<Self>) {
|
||||
self.editor.update(ctx, |editor, ctx| {
|
||||
editor.clear_buffer(ctx);
|
||||
});
|
||||
}
|
||||
|
||||
fn handle_editor_event(&mut self, event: &EditorEvent, ctx: &mut ViewContext<Self>) {
|
||||
match event {
|
||||
EditorEvent::Enter => {
|
||||
let prompt = self
|
||||
.editor
|
||||
.read(ctx, |editor, ctx| editor.buffer_text(ctx).trim().to_owned());
|
||||
if prompt.is_empty() {
|
||||
return;
|
||||
}
|
||||
|
||||
if !(self.validator)(&prompt) {
|
||||
self.has_error = true;
|
||||
ctx.notify();
|
||||
} else {
|
||||
self.has_error = false;
|
||||
self.clear_buffer(ctx);
|
||||
ctx.emit(GlowingEditorEvent::Submit(prompt));
|
||||
}
|
||||
}
|
||||
EditorEvent::Escape => ctx.emit(GlowingEditorEvent::Cancel),
|
||||
// Clear error state when user types (since this is submit-only validation)
|
||||
EditorEvent::Edited(_) => {
|
||||
if self.has_error {
|
||||
self.has_error = false;
|
||||
ctx.notify();
|
||||
}
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub enum GlowingEditorEvent {
|
||||
Submit(String),
|
||||
Cancel,
|
||||
}
|
||||
|
||||
impl Entity for GlowingEditor {
|
||||
type Event = GlowingEditorEvent;
|
||||
}
|
||||
|
||||
impl TypedActionView for GlowingEditor {
|
||||
type Action = ();
|
||||
}
|
||||
|
||||
impl View for GlowingEditor {
|
||||
fn ui_name() -> &'static str {
|
||||
"GlowingEditor"
|
||||
}
|
||||
|
||||
fn on_focus(&mut self, focus_ctx: &FocusContext, ctx: &mut ViewContext<Self>) {
|
||||
if focus_ctx.is_self_focused() {
|
||||
ctx.focus(&self.editor);
|
||||
}
|
||||
}
|
||||
|
||||
fn render(&self, app: &AppContext) -> Box<dyn Element> {
|
||||
let appearance = Appearance::as_ref(app);
|
||||
let theme = appearance.theme();
|
||||
|
||||
let input_box = Shrinkable::new(
|
||||
1.,
|
||||
Align::new(
|
||||
appearance
|
||||
.ui_builder()
|
||||
.text_input(self.editor.clone())
|
||||
.with_style(UiComponentStyles {
|
||||
font_weight: Some(Weight::Semibold),
|
||||
border_style: Some(BorderStyle::None),
|
||||
border_width: Some(0.),
|
||||
background: Some(ColorU::transparent_black().into()),
|
||||
padding: Some(Coords::uniform(10.).left(0.)),
|
||||
..Default::default()
|
||||
})
|
||||
.build()
|
||||
.finish(),
|
||||
)
|
||||
.finish(),
|
||||
)
|
||||
.finish();
|
||||
|
||||
let font_size = appearance.ui_font_size() + 2.;
|
||||
let agent_icon = Container::new(
|
||||
ConstrainedBox::new(
|
||||
Icon::AgentMode
|
||||
.to_warpui_icon(theme.sub_text_color(theme.background()))
|
||||
.finish(),
|
||||
)
|
||||
.with_height(font_size)
|
||||
.with_width(font_size)
|
||||
.finish(),
|
||||
)
|
||||
.with_margin_left(ICON_MARGIN_LEFT)
|
||||
.with_margin_right(ICON_MARGIN_RIGHT)
|
||||
.finish();
|
||||
|
||||
let editor_content = ConstrainedBox::new(
|
||||
Flex::row()
|
||||
.with_cross_axis_alignment(CrossAxisAlignment::Center)
|
||||
.with_main_axis_alignment(MainAxisAlignment::Start)
|
||||
.with_children([agent_icon, input_box])
|
||||
.finish(),
|
||||
)
|
||||
.with_min_height(PROMPT_INPUT_HEIGHT);
|
||||
|
||||
let border_fill = if self.has_error {
|
||||
theme.ui_error_color()
|
||||
} else {
|
||||
theme.outline().into_solid()
|
||||
};
|
||||
|
||||
let shadow_color = if self.has_error {
|
||||
ColorU::new(255, 0, 0, 100) // Red shadow with higher opacity for error
|
||||
} else {
|
||||
ColorU::new(255, 143, 253, 15) // Default purple shadow
|
||||
};
|
||||
|
||||
Container::new(editor_content.finish())
|
||||
.with_border(border_fill)
|
||||
.with_corner_radius(CornerRadius::with_all(Radius::Pixels(4.)))
|
||||
.with_drop_shadow(
|
||||
DropShadow::new_with_standard_offset_and_spread(shadow_color)
|
||||
.with_offset(Vector2F::zero()),
|
||||
)
|
||||
.with_background(theme.background().into_solid())
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user