use std::borrow::Cow; use pathfinder_color::ColorU;use pathfinder_geometry::vector::Vector2F;use warp_core::features::FeatureFlag;use warp_core::ui::Icon;use warp_core::ui::appearance::Appearance;use warp_core::ui::color::blend::Blend as _;use warp_core::ui::theme::color::internal_colors;use warpui::elements::{ ChildAnchor, ConstrainedBox, Container, CornerRadius, CrossAxisAlignment, DropShadow, Expanded, Flex, Hoverable, MouseStateHandle, OffsetPositioning, ParentAnchor, ParentElement as _, ParentOffsetBounds, Radius, Stack,};use warpui::fonts::Weight;use warpui::keymap::EditableBinding;use warpui::platform::file_picker::FilePickerError;use warpui::platform::{Cursor, FilePickerConfiguration};use warpui::ui_components::components::{UiComponent as _, UiComponentStyles};use warpui::{ AppContext, Element, Entity, SingletonEntity as _, TypedActionView, View, ViewContext,}; use crate::util::bindings::{BindingGroup, CustomAction, keybinding_name_to_display_string}; const BUTTON_MIN_WIDTH: f32 = 149.; pub fn init(app: &mut AppContext) { use warpui::keymap::macros::*; app.register_editable_bindings([ EditableBinding::new( "project_buttons:open_repository", "Open repository", ProjectButtonsAction::OpenRepository, ) .with_context_predicate(id!("ProjectButons")) .with_group(BindingGroup::Folders.as_str()) .with_custom_action(CustomAction::OpenRepository), EditableBinding::new( "project_buttons:create_new_project", "Create new project", ProjectButtonsAction::CreateProject, ) .with_context_predicate(id!("ProjectButons")) .with_enabled(|| FeatureFlag::CreateProjectFlow.is_enabled()) .with_mac_key_binding("cmd-shift-N") .with_linux_or_windows_key_binding("alt-shift-N"), ]);} #[derive(Default)]struct StateHandles { open_repo_button: MouseStateHandle, create_project_button: MouseStateHandle, clone_repo_button: MouseStateHandle,} pub struct ProjectButtons { state_handles: StateHandles,} struct TooltipData { text: String, keybinding: Option<String>,} impl ProjectButtons { pub fn new(_ctx: &mut ViewContext<Self>) -> Self { Self { state_handles: Default::default(), } } pub fn open_repository(ctx: &mut ViewContext<Self>) { ctx.open_file_picker( move |result, ctx| { if let Some(path_result) = result.map(|paths| paths.into_iter().next()).transpose() { ctx.emit(ProjectButtonsEvent::OpenRepository(path_result)); } }, FilePickerConfiguration::new().folders_only(), ); } fn glowing_button( &self, label_text: impl Into<Cow<'static, str>> + Clone, icon: Icon, action: ProjectButtonsAction, tooltip: TooltipData, mouse_state: MouseStateHandle, app: &AppContext, ) -> Box<dyn Element> { let appearance = Appearance::as_ref(app); let theme = appearance.theme(); let icon_color = internal_colors::fg_overlay_6(theme); let label = appearance .ui_builder() .paragraph(label_text.clone()) .with_style(UiComponentStyles { font_weight: Some(Weight::Semibold), ..Default::default() }) .build() .finish(); Hoverable::new(mouse_state, move |state| { let icon_el = Container::new( ConstrainedBox::new(icon.to_warpui_icon(icon_color).finish()) .with_height(20.) .with_width(20.) .finish(), ) .finish(); let vertical_content = Flex::column() .with_cross_axis_alignment(CrossAxisAlignment::Start) .with_child(icon_el) .with_child(Container::new(label).with_margin_top(4.).finish()) .finish(); let base = ConstrainedBox::new( Container::new( Container::new(vertical_content) .with_uniform_padding(16.) .finish(), ) .with_border(theme.outline().into_solid()) .with_corner_radius(CornerRadius::with_all(Radius::Pixels(4.))) .with_drop_shadow( DropShadow::new_with_standard_offset_and_spread(ColorU::new(255, 143, 253, 15)) .with_offset(Vector2F::zero()), ) .with_background(if state.is_hovered() { theme .background() .blend(&theme.surface_overlay_1()) .into_solid() } else { theme.background().into_solid() }) .finish(), ) .with_min_width(BUTTON_MIN_WIDTH) .finish(); // Optional tooltip with keybinding string if state.is_hovered() { let tooltip = if let Some(keybinding) = tooltip.keybinding { appearance .ui_builder() .tool_tip_with_sublabel(tooltip.text, keybinding) .build() .finish() } else { appearance .ui_builder() .tool_tip(tooltip.text) .build() .finish() }; let mut stack = Stack::new(); stack.add_child(base); let offset = OffsetPositioning::offset_from_parent( Vector2F::new(0., 4.), ParentOffsetBounds::WindowByPosition, ParentAnchor::BottomMiddle, ChildAnchor::TopMiddle, ); stack.add_positioned_overlay_child(tooltip, offset); stack.finish() } else { base } }) .with_cursor(Cursor::PointingHand) .on_click(move |ctx, _, _| ctx.dispatch_typed_action(action)) .finish() }} pub enum ProjectButtonsEvent { OpenRepository(Result<String, FilePickerError>), CreateProject, CloneRepository,} impl Entity for ProjectButtons { type Event = ProjectButtonsEvent;} #[derive(Clone, Copy, Debug)]pub enum ProjectButtonsAction { OpenRepository, CreateProject, CloneRepository,} impl TypedActionView for ProjectButtons { type Action = ProjectButtonsAction; fn handle_action(&mut self, action: &Self::Action, ctx: &mut ViewContext<Self>) { match action { ProjectButtonsAction::OpenRepository => Self::open_repository(ctx), ProjectButtonsAction::CreateProject => ctx.emit(ProjectButtonsEvent::CreateProject), ProjectButtonsAction::CloneRepository => ctx.emit(ProjectButtonsEvent::CloneRepository), } }} impl View for ProjectButtons { fn ui_name() -> &'static str { "ProjectButons" } fn render(&self, app: &AppContext) -> Box<dyn Element> { let mut row = Flex::row(); if FeatureFlag::CreateProjectFlow.is_enabled() { row.add_children([ Container::new(self.glowing_button( "Create new project", Icon::Plus, ProjectButtonsAction::CreateProject, TooltipData { text: "Create and initialize a brand new project".to_string(), keybinding: keybinding_name_to_display_string( "project_buttons:create_new_project", app, ), }, self.state_handles.create_project_button.clone(), app, )) .with_margin_right(16.) .finish(), Container::new(self.glowing_button( "Open repository", Icon::Folder, ProjectButtonsAction::OpenRepository, TooltipData { text: "Open an existing local folder or repository".to_string(), keybinding: keybinding_name_to_display_string( "project_buttons:open_repository", app, ), }, self.state_handles.open_repo_button.clone(), app, )) .with_margin_right(16.) .finish(), self.glowing_button( "Clone repository", Icon::Duplicate, ProjectButtonsAction::CloneRepository, TooltipData { text: "Clone a repo from GitHub or another source".to_string(), keybinding: None, }, self.state_handles.clone_repo_button.clone(), app, ), ]); } else { row.add_child( Expanded::new( 1., self.glowing_button( "Open repository", Icon::Plus, ProjectButtonsAction::CreateProject, TooltipData { text: "Open an existing local folder or repository".to_string(), keybinding: keybinding_name_to_display_string( "project_buttons:open_repository", app, ), }, self.state_handles.create_project_button.clone(), app, ), ) .finish(), ); } row.finish() }}
use std::borrow::Cow;
use pathfinder_color::ColorU;
use pathfinder_geometry::vector::Vector2F;
use warp_core::features::FeatureFlag;
use warp_core::ui::Icon;
use warp_core::ui::appearance::Appearance;
use warp_core::ui::color::blend::Blend as _;
use warp_core::ui::theme::color::internal_colors;
use warpui::elements::{
ChildAnchor, ConstrainedBox, Container, CornerRadius, CrossAxisAlignment, DropShadow, Expanded,
Flex, Hoverable, MouseStateHandle, OffsetPositioning, ParentAnchor, ParentElement as _,
ParentOffsetBounds, Radius, Stack,
};
use warpui::fonts::Weight;
use warpui::keymap::EditableBinding;
use warpui::platform::file_picker::FilePickerError;
use warpui::platform::{Cursor, FilePickerConfiguration};
use warpui::ui_components::components::{UiComponent as _, UiComponentStyles};
use warpui::{
AppContext, Element, Entity, SingletonEntity as _, TypedActionView, View, ViewContext,
};
use crate::util::bindings::{BindingGroup, CustomAction, keybinding_name_to_display_string};
const BUTTON_MIN_WIDTH: f32 = 149.;
pub fn init(app: &mut AppContext) {
use warpui::keymap::macros::*;
app.register_editable_bindings([
EditableBinding::new(
"project_buttons:open_repository",
"Open repository",
ProjectButtonsAction::OpenRepository,
)
.with_context_predicate(id!("ProjectButons"))
.with_group(BindingGroup::Folders.as_str())
.with_custom_action(CustomAction::OpenRepository),
EditableBinding::new(
"project_buttons:create_new_project",
"Create new project",
ProjectButtonsAction::CreateProject,
)
.with_context_predicate(id!("ProjectButons"))
.with_enabled(|| FeatureFlag::CreateProjectFlow.is_enabled())
.with_mac_key_binding("cmd-shift-N")
.with_linux_or_windows_key_binding("alt-shift-N"),
]);
}
#[derive(Default)]
struct StateHandles {
open_repo_button: MouseStateHandle,
create_project_button: MouseStateHandle,
clone_repo_button: MouseStateHandle,
}
pub struct ProjectButtons {
state_handles: StateHandles,
}
struct TooltipData {
text: String,
keybinding: Option<String>,
}
impl ProjectButtons {
pub fn new(_ctx: &mut ViewContext<Self>) -> Self {
Self {
state_handles: Default::default(),
}
}
pub fn open_repository(ctx: &mut ViewContext<Self>) {
ctx.open_file_picker(
move |result, ctx| {
if let Some(path_result) = result.map(|paths| paths.into_iter().next()).transpose()
{
ctx.emit(ProjectButtonsEvent::OpenRepository(path_result));
}
},
FilePickerConfiguration::new().folders_only(),
);
}
fn glowing_button(
&self,
label_text: impl Into<Cow<'static, str>> + Clone,
icon: Icon,
action: ProjectButtonsAction,
tooltip: TooltipData,
mouse_state: MouseStateHandle,
app: &AppContext,
) -> Box<dyn Element> {
let appearance = Appearance::as_ref(app);
let theme = appearance.theme();
let icon_color = internal_colors::fg_overlay_6(theme);
let label = appearance
.ui_builder()
.paragraph(label_text.clone())
.with_style(UiComponentStyles {
font_weight: Some(Weight::Semibold),
..Default::default()
})
.build()
.finish();
Hoverable::new(mouse_state, move |state| {
let icon_el = Container::new(
ConstrainedBox::new(icon.to_warpui_icon(icon_color).finish())
.with_height(20.)
.with_width(20.)
.finish(),
)
.finish();
let vertical_content = Flex::column()
.with_cross_axis_alignment(CrossAxisAlignment::Start)
.with_child(icon_el)
.with_child(Container::new(label).with_margin_top(4.).finish())
.finish();
let base = ConstrainedBox::new(
Container::new(
Container::new(vertical_content)
.with_uniform_padding(16.)
.finish(),
)
.with_border(theme.outline().into_solid())
.with_corner_radius(CornerRadius::with_all(Radius::Pixels(4.)))
.with_drop_shadow(
DropShadow::new_with_standard_offset_and_spread(ColorU::new(255, 143, 253, 15))
.with_offset(Vector2F::zero()),
)
.with_background(if state.is_hovered() {
theme
.background()
.blend(&theme.surface_overlay_1())
.into_solid()
} else {
theme.background().into_solid()
})
.finish(),
)
.with_min_width(BUTTON_MIN_WIDTH)
.finish();
// Optional tooltip with keybinding string
if state.is_hovered() {
let tooltip = if let Some(keybinding) = tooltip.keybinding {
appearance
.ui_builder()
.tool_tip_with_sublabel(tooltip.text, keybinding)
.build()
.finish()
} else {
appearance
.ui_builder()
.tool_tip(tooltip.text)
.build()
.finish()
};
let mut stack = Stack::new();
stack.add_child(base);
let offset = OffsetPositioning::offset_from_parent(
Vector2F::new(0., 4.),
ParentOffsetBounds::WindowByPosition,
ParentAnchor::BottomMiddle,
ChildAnchor::TopMiddle,
);
stack.add_positioned_overlay_child(tooltip, offset);
stack.finish()
} else {
base
}
})
.with_cursor(Cursor::PointingHand)
.on_click(move |ctx, _, _| ctx.dispatch_typed_action(action))
.finish()
}
}
pub enum ProjectButtonsEvent {
OpenRepository(Result<String, FilePickerError>),
CreateProject,
CloneRepository,
}
impl Entity for ProjectButtons {
type Event = ProjectButtonsEvent;
}
#[derive(Clone, Copy, Debug)]
pub enum ProjectButtonsAction {
OpenRepository,
CreateProject,
CloneRepository,
}
impl TypedActionView for ProjectButtons {
type Action = ProjectButtonsAction;
fn handle_action(&mut self, action: &Self::Action, ctx: &mut ViewContext<Self>) {
match action {
ProjectButtonsAction::OpenRepository => Self::open_repository(ctx),
ProjectButtonsAction::CreateProject => ctx.emit(ProjectButtonsEvent::CreateProject),
ProjectButtonsAction::CloneRepository => ctx.emit(ProjectButtonsEvent::CloneRepository),
}
}
}
impl View for ProjectButtons {
fn ui_name() -> &'static str {
"ProjectButons"
}
fn render(&self, app: &AppContext) -> Box<dyn Element> {
let mut row = Flex::row();
if FeatureFlag::CreateProjectFlow.is_enabled() {
row.add_children([
Container::new(self.glowing_button(
"Create new project",
Icon::Plus,
ProjectButtonsAction::CreateProject,
TooltipData {
text: "Create and initialize a brand new project".to_string(),
keybinding: keybinding_name_to_display_string(
"project_buttons:create_new_project",
app,
),
},
self.state_handles.create_project_button.clone(),
app,
))
.with_margin_right(16.)
.finish(),
Container::new(self.glowing_button(
"Open repository",
Icon::Folder,
ProjectButtonsAction::OpenRepository,
TooltipData {
text: "Open an existing local folder or repository".to_string(),
keybinding: keybinding_name_to_display_string(
"project_buttons:open_repository",
app,
),
},
self.state_handles.open_repo_button.clone(),
app,
))
.with_margin_right(16.)
.finish(),
self.glowing_button(
"Clone repository",
Icon::Duplicate,
ProjectButtonsAction::CloneRepository,
TooltipData {
text: "Clone a repo from GitHub or another source".to_string(),
keybinding: None,
},
self.state_handles.clone_repo_button.clone(),
app,
),
]);
} else {
row.add_child(
Expanded::new(
1.,
self.glowing_button(
"Open repository",
Icon::Plus,
ProjectButtonsAction::CreateProject,
TooltipData {
text: "Open an existing local folder or repository".to_string(),
keybinding: keybinding_name_to_display_string(
"project_buttons:open_repository",
app,
),
},
self.state_handles.create_project_button.clone(),
app,
),
)
.finish(),
);
}
row.finish()
}
}