271 lines
9.1 KiB
Rust
271 lines
9.1 KiB
Rust
use pathfinder_geometry::vector::vec2f;
|
|
use warp_core::ui::{color::coloru_with_opacity, theme::Fill};
|
|
use warpui::{
|
|
elements::{
|
|
Align, ChildAnchor, ConstrainedBox, Container, CrossAxisAlignment, Flex, Hoverable,
|
|
MouseStateHandle, OffsetPositioning, ParentAnchor, ParentElement, ParentOffsetBounds,
|
|
Stack, Text,
|
|
},
|
|
fonts::Weight,
|
|
keymap::{FixedBinding, Keystroke},
|
|
platform::Cursor,
|
|
ui_components::{
|
|
button::ButtonVariant,
|
|
components::{Coords, UiComponent, UiComponentStyles},
|
|
},
|
|
AppContext, Element, Entity, EntityId, SingletonEntity, TypedActionView, View, ViewContext,
|
|
};
|
|
|
|
use crate::{
|
|
ai::agent::{conversation::AIConversationId, AIAgentExchangeId},
|
|
appearance::Appearance,
|
|
ui_components::dialog::{dialog_styles, Dialog},
|
|
ui_components::icons::Icon,
|
|
};
|
|
|
|
pub fn init(app: &mut AppContext) {
|
|
use warpui::keymap::macros::*;
|
|
|
|
app.register_fixed_bindings([
|
|
FixedBinding::new(
|
|
"escape",
|
|
RewindConfirmationAction::Cancel,
|
|
id!(RewindConfirmationDialog::ui_name()),
|
|
),
|
|
FixedBinding::new(
|
|
"enter",
|
|
RewindConfirmationAction::Confirm,
|
|
id!(RewindConfirmationDialog::ui_name()),
|
|
),
|
|
]);
|
|
}
|
|
|
|
const DIALOG_WIDTH: f32 = 460.;
|
|
|
|
/// Data needed to perform the rewind action after confirmation
|
|
#[derive(Clone)]
|
|
pub struct RewindDialogSource {
|
|
pub ai_block_view_id: EntityId,
|
|
pub exchange_id: AIAgentExchangeId,
|
|
pub conversation_id: AIConversationId,
|
|
}
|
|
|
|
pub struct RewindConfirmationDialog {
|
|
cancel_mouse_state: MouseStateHandle,
|
|
confirm_mouse_state: MouseStateHandle,
|
|
/// Source will be None if dialog was never opened
|
|
rewind_source: Option<RewindDialogSource>,
|
|
}
|
|
|
|
impl Default for RewindConfirmationDialog {
|
|
fn default() -> Self {
|
|
Self::new()
|
|
}
|
|
}
|
|
|
|
impl RewindConfirmationDialog {
|
|
pub fn new() -> Self {
|
|
Self {
|
|
cancel_mouse_state: Default::default(),
|
|
confirm_mouse_state: Default::default(),
|
|
rewind_source: None,
|
|
}
|
|
}
|
|
|
|
pub fn set_rewind_source(&mut self, source: RewindDialogSource) {
|
|
self.rewind_source = Some(source);
|
|
}
|
|
}
|
|
|
|
impl Entity for RewindConfirmationDialog {
|
|
type Event = RewindConfirmationEvent;
|
|
}
|
|
|
|
impl View for RewindConfirmationDialog {
|
|
fn ui_name() -> &'static str {
|
|
"RewindConfirmationDialog"
|
|
}
|
|
|
|
fn on_focus(&mut self, _focus_ctx: &warpui::FocusContext, ctx: &mut ViewContext<Self>) {
|
|
ctx.focus_self();
|
|
}
|
|
|
|
fn render(&self, app: &AppContext) -> Box<dyn Element> {
|
|
let appearance = Appearance::as_ref(app);
|
|
let theme = appearance.theme();
|
|
|
|
let button_style = UiComponentStyles {
|
|
font_size: Some(14.),
|
|
font_weight: Some(Weight::Bold),
|
|
height: Some(40.),
|
|
..Default::default()
|
|
};
|
|
|
|
// Build rewind button label with Enter keyboard shortcut indicator
|
|
let enter_keystroke = Keystroke::parse("enter").expect("Valid keystroke");
|
|
let text_color = theme.main_text_color(theme.accent()).into_solid();
|
|
let rewind_button_label = Flex::row()
|
|
.with_cross_axis_alignment(CrossAxisAlignment::Center)
|
|
.with_child(
|
|
Text::new_inline("Rewind", appearance.ui_font_family(), 14.)
|
|
.with_color(text_color)
|
|
.finish(),
|
|
)
|
|
.with_child(
|
|
Container::new(
|
|
appearance
|
|
.ui_builder()
|
|
.keyboard_shortcut(&enter_keystroke)
|
|
.with_style(UiComponentStyles {
|
|
font_size: Some(10.),
|
|
height: Some(16.),
|
|
padding: Some(Coords::uniform(1.)),
|
|
border_width: Some(1.),
|
|
border_color: Some(coloru_with_opacity(text_color, 60).into()),
|
|
font_color: Some(text_color),
|
|
font_family_id: Some(appearance.ui_font_family()),
|
|
..Default::default()
|
|
})
|
|
.with_line_height_ratio(1.0)
|
|
.build()
|
|
.finish(),
|
|
)
|
|
.with_margin_left(8.)
|
|
.finish(),
|
|
)
|
|
.finish();
|
|
|
|
let rewind_button = appearance
|
|
.ui_builder()
|
|
.button(ButtonVariant::Accent, self.confirm_mouse_state.clone())
|
|
.with_custom_label(rewind_button_label)
|
|
.with_style(UiComponentStyles {
|
|
padding: Some(Coords {
|
|
left: 16.,
|
|
right: 16.,
|
|
top: 0.,
|
|
bottom: 0.,
|
|
}),
|
|
..button_style
|
|
})
|
|
.build()
|
|
.with_cursor(Cursor::PointingHand)
|
|
.on_click(move |ctx, _, _| ctx.dispatch_typed_action(RewindConfirmationAction::Confirm))
|
|
.finish();
|
|
|
|
let cancel_text_color = theme.sub_text_color(theme.surface_2());
|
|
let cancel_button = Container::new(
|
|
Hoverable::new(self.cancel_mouse_state.clone(), move |mouse_state| {
|
|
let color = if mouse_state.is_mouse_over_element() {
|
|
theme.main_text_color(theme.surface_2())
|
|
} else {
|
|
cancel_text_color
|
|
};
|
|
Text::new_inline("Cancel", appearance.ui_font_family(), 14.)
|
|
.with_color(color.into_solid())
|
|
.finish()
|
|
})
|
|
.with_cursor(Cursor::PointingHand)
|
|
.on_click(move |ctx, _, _| ctx.dispatch_typed_action(RewindConfirmationAction::Cancel))
|
|
.finish(),
|
|
)
|
|
.with_margin_right(16.)
|
|
.finish();
|
|
|
|
// Info text with icon
|
|
let info_color = theme.sub_text_color(theme.surface_2());
|
|
let info_row = Flex::row()
|
|
.with_cross_axis_alignment(CrossAxisAlignment::Center)
|
|
.with_child(
|
|
Container::new(
|
|
ConstrainedBox::new(Icon::Info.to_warpui_icon(info_color).finish())
|
|
.with_height(14.)
|
|
.with_width(14.)
|
|
.finish(),
|
|
)
|
|
.with_margin_right(6.)
|
|
.finish(),
|
|
)
|
|
.with_child(
|
|
Text::new_inline(
|
|
"Rewinding does not affect files edited manually or via shell commands.",
|
|
appearance.ui_font_family(),
|
|
12.,
|
|
)
|
|
.with_color(info_color.into_solid())
|
|
.finish(),
|
|
)
|
|
.finish();
|
|
|
|
let dialog = Container::new(
|
|
Dialog::new(
|
|
"Rewind".into(),
|
|
Some(
|
|
"Are you sure you want to rewind? This will restore your code and conversation to before this point, and cancel any commands the agent is currently running. A copy of the original conversation will be saved in your conversation history."
|
|
.into(),
|
|
),
|
|
UiComponentStyles {
|
|
width: Some(DIALOG_WIDTH),
|
|
padding: Some(Coords::uniform(24.)),
|
|
..dialog_styles(appearance)
|
|
},
|
|
)
|
|
.with_child(info_row)
|
|
.with_bottom_row_child(cancel_button)
|
|
.with_bottom_row_child(rewind_button)
|
|
.build()
|
|
.finish(),
|
|
)
|
|
.with_margin_top(35.)
|
|
.finish();
|
|
|
|
// Stack needed so that dialog can get bounds information
|
|
let mut stack = Stack::new();
|
|
stack.add_positioned_child(
|
|
dialog,
|
|
OffsetPositioning::offset_from_parent(
|
|
vec2f(0., 0.),
|
|
ParentOffsetBounds::WindowByPosition,
|
|
ParentAnchor::Center,
|
|
ChildAnchor::Center,
|
|
),
|
|
);
|
|
|
|
// This blurs the background and makes it uninteractable
|
|
Container::new(Align::new(stack.finish()).finish())
|
|
.with_background_color(Fill::blur().into())
|
|
.with_corner_radius(app.windows().window_corner_radius())
|
|
.finish()
|
|
}
|
|
}
|
|
|
|
pub enum RewindConfirmationEvent {
|
|
Confirm { rewind_source: RewindDialogSource },
|
|
Cancel,
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
pub enum RewindConfirmationAction {
|
|
Confirm,
|
|
Cancel,
|
|
}
|
|
|
|
impl TypedActionView for RewindConfirmationDialog {
|
|
type Action = RewindConfirmationAction;
|
|
|
|
fn handle_action(&mut self, action: &RewindConfirmationAction, ctx: &mut ViewContext<Self>) {
|
|
match action {
|
|
RewindConfirmationAction::Confirm => {
|
|
let Some(rewind_source) = self.rewind_source.clone() else {
|
|
log::error!("Rewind confirm button pressed with no rewind source");
|
|
return;
|
|
};
|
|
ctx.emit(RewindConfirmationEvent::Confirm { rewind_source });
|
|
}
|
|
RewindConfirmationAction::Cancel => {
|
|
ctx.emit(RewindConfirmationEvent::Cancel);
|
|
}
|
|
}
|
|
}
|
|
}
|