249 lines
9.0 KiB
Rust
249 lines
9.0 KiB
Rust
use warp_util::path::user_friendly_path;
|
|
use warpui::{
|
|
elements::{
|
|
Border, ConstrainedBox, Container, CornerRadius, CrossAxisAlignment, Flex, MainAxisSize,
|
|
MouseStateHandle, ParentElement, Radius, Text,
|
|
},
|
|
platform::Cursor,
|
|
ui_components::{
|
|
button::{ButtonTooltipPosition, ButtonVariant},
|
|
components::{UiComponent, UiComponentStyles},
|
|
},
|
|
AppContext, Element, SingletonEntity,
|
|
};
|
|
|
|
use crate::{
|
|
appearance::Appearance, settings::ai::DefaultSessionMode, tab_configs::TabConfig,
|
|
terminal::available_shells::AvailableShell, workspace::WorkspaceAction,
|
|
};
|
|
|
|
pub(crate) const SIDECAR_WIDTH: f32 = 260.;
|
|
const SIDECAR_PADDING: f32 = 12.;
|
|
|
|
/// Describes what the sidecar is showing, which determines which buttons appear.
|
|
#[derive(Clone, Debug)]
|
|
pub(crate) enum SidecarItemKind {
|
|
/// A built-in item (Terminal, a specific shell, Agent, Cloud Oz).
|
|
BuiltIn {
|
|
name: String,
|
|
default_mode: DefaultSessionMode,
|
|
shell: Option<AvailableShell>,
|
|
},
|
|
/// A user-created tab config loaded from disk.
|
|
UserTabConfig { config: TabConfig },
|
|
}
|
|
|
|
#[derive(Default)]
|
|
pub(crate) struct SidecarMouseStates {
|
|
pub(crate) make_default: MouseStateHandle,
|
|
pub(crate) edit_config: MouseStateHandle,
|
|
pub(crate) remove_config: MouseStateHandle,
|
|
}
|
|
|
|
/// Renders the action sidecar panel as a raw element tree.
|
|
/// Called directly from the Workspace render method (not via ChildView).
|
|
pub(crate) fn render_action_sidecar(
|
|
item: &SidecarItemKind,
|
|
mouse_states: &SidecarMouseStates,
|
|
is_already_default: bool,
|
|
app: &AppContext,
|
|
) -> Box<dyn Element> {
|
|
let appearance = Appearance::as_ref(app);
|
|
let theme = appearance.theme();
|
|
let font_family = appearance.ui_font_family();
|
|
let font_size = appearance.ui_font_size();
|
|
|
|
let mut column = Flex::column()
|
|
.with_cross_axis_alignment(CrossAxisAlignment::Start)
|
|
.with_main_axis_size(MainAxisSize::Min);
|
|
|
|
// Title
|
|
let title = match item {
|
|
SidecarItemKind::BuiltIn { name, .. } => name.clone(),
|
|
SidecarItemKind::UserTabConfig { config } => config.name.clone(),
|
|
};
|
|
column.add_child(
|
|
Container::new(
|
|
Text::new_inline(title, font_family, font_size + 1.)
|
|
.with_color(theme.main_text_color(theme.surface_2()).into())
|
|
.finish(),
|
|
)
|
|
.with_margin_bottom(4.)
|
|
.finish(),
|
|
);
|
|
|
|
// Subtitle (file path for user configs)
|
|
if let SidecarItemKind::UserTabConfig { config } = item {
|
|
if let Some(path) = &config.source_path {
|
|
let raw_path = path.to_string_lossy().into_owned();
|
|
let home_dir = dirs::home_dir();
|
|
let path_str =
|
|
user_friendly_path(&raw_path, home_dir.as_ref().and_then(|h| h.to_str()))
|
|
.into_owned();
|
|
column.add_child(
|
|
Container::new(
|
|
Text::new_inline(path_str, font_family, font_size - 1.)
|
|
.with_color(theme.sub_text_color(theme.surface_2()).into())
|
|
.finish(),
|
|
)
|
|
.with_margin_bottom(8.)
|
|
.finish(),
|
|
);
|
|
}
|
|
}
|
|
|
|
let primary_text_color = theme.main_text_color(theme.surface_2());
|
|
let button_style = UiComponentStyles {
|
|
font_size: Some(12.),
|
|
font_weight: Some(warpui::fonts::Weight::Bold),
|
|
font_color: Some(primary_text_color.into()),
|
|
padding: Some(warpui::ui_components::components::Coords {
|
|
top: 4.,
|
|
bottom: 4.,
|
|
left: 8.,
|
|
right: 8.,
|
|
}),
|
|
..Default::default()
|
|
};
|
|
|
|
let make_default_action = match item {
|
|
SidecarItemKind::BuiltIn {
|
|
default_mode,
|
|
shell,
|
|
..
|
|
} => WorkspaceAction::TabConfigSidecarMakeDefault {
|
|
mode: *default_mode,
|
|
tab_config_path: None,
|
|
shell: shell.clone(),
|
|
},
|
|
SidecarItemKind::UserTabConfig { config } => WorkspaceAction::TabConfigSidecarMakeDefault {
|
|
mode: DefaultSessionMode::TabConfig,
|
|
tab_config_path: config.source_path.clone(),
|
|
shell: None,
|
|
},
|
|
};
|
|
|
|
// "Make default" button (always shown; visually disabled with tooltip when already the default)
|
|
let make_default_button = if is_already_default {
|
|
let disabled_style = UiComponentStyles {
|
|
font_color: Some(theme.disabled_text_color(theme.surface_2()).into()),
|
|
border_color: Some(theme.outline().into()),
|
|
..button_style
|
|
};
|
|
appearance
|
|
.ui_builder()
|
|
.button(ButtonVariant::Outlined, mouse_states.make_default.clone())
|
|
.with_centered_text_label("Make default".into())
|
|
.with_style(disabled_style)
|
|
.with_tooltip({
|
|
let ui_builder = appearance.ui_builder().clone();
|
|
move || {
|
|
ui_builder
|
|
.tool_tip("Already the default".into())
|
|
.build()
|
|
.finish()
|
|
}
|
|
})
|
|
.with_tooltip_position(ButtonTooltipPosition::Above)
|
|
.set_clicked_styles(None)
|
|
.build()
|
|
.finish()
|
|
} else {
|
|
appearance
|
|
.ui_builder()
|
|
.button(ButtonVariant::Outlined, mouse_states.make_default.clone())
|
|
.with_centered_text_label("Make default".into())
|
|
.with_style(button_style)
|
|
.build()
|
|
.with_cursor(Cursor::PointingHand)
|
|
.on_click(move |ctx: &mut warpui::elements::EventContext, _, _| {
|
|
ctx.dispatch_typed_action(make_default_action.clone())
|
|
})
|
|
.finish()
|
|
};
|
|
column.add_child(
|
|
ConstrainedBox::new(make_default_button)
|
|
.with_max_width(SIDECAR_WIDTH - SIDECAR_PADDING * 2.)
|
|
.finish(),
|
|
);
|
|
|
|
// "Edit config" and "Remove" buttons (only for user tab configs)
|
|
if let SidecarItemKind::UserTabConfig { config } = item {
|
|
if let Some(config_path) = &config.source_path {
|
|
let edit_path = config_path.clone();
|
|
let edit_button = appearance
|
|
.ui_builder()
|
|
.button(ButtonVariant::Outlined, mouse_states.edit_config.clone())
|
|
.with_centered_text_label("Edit config".into())
|
|
.with_style(button_style)
|
|
.build()
|
|
.with_cursor(Cursor::PointingHand)
|
|
.on_click(move |ctx: &mut warpui::elements::EventContext, _, _| {
|
|
ctx.dispatch_typed_action(WorkspaceAction::TabConfigSidecarEditConfig {
|
|
path: edit_path.clone(),
|
|
})
|
|
})
|
|
.finish();
|
|
column.add_child(
|
|
Container::new(
|
|
ConstrainedBox::new(edit_button)
|
|
.with_max_width(SIDECAR_WIDTH - SIDECAR_PADDING * 2.)
|
|
.finish(),
|
|
)
|
|
.with_margin_top(4.)
|
|
.finish(),
|
|
);
|
|
|
|
let remove_name = config.name.clone();
|
|
let remove_path = config_path.clone();
|
|
let red_color = theme.ansi_fg_red();
|
|
let remove_style = UiComponentStyles {
|
|
font_color: Some(red_color),
|
|
border_color: Some(red_color.into()),
|
|
..button_style
|
|
};
|
|
let remove_button = appearance
|
|
.ui_builder()
|
|
.button(ButtonVariant::Outlined, mouse_states.remove_config.clone())
|
|
.with_centered_text_label("Remove".into())
|
|
.with_style(remove_style)
|
|
.with_hovered_styles(UiComponentStyles {
|
|
border_color: Some(theme.accent().into()),
|
|
..Default::default()
|
|
})
|
|
.build()
|
|
.with_cursor(Cursor::PointingHand)
|
|
.on_click(move |ctx: &mut warpui::elements::EventContext, _, _| {
|
|
ctx.dispatch_typed_action(WorkspaceAction::TabConfigSidecarRemoveConfig {
|
|
name: remove_name.clone(),
|
|
path: remove_path.clone(),
|
|
})
|
|
})
|
|
.finish();
|
|
column.add_child(
|
|
Container::new(
|
|
ConstrainedBox::new(remove_button)
|
|
.with_max_width(SIDECAR_WIDTH - SIDECAR_PADDING * 2.)
|
|
.finish(),
|
|
)
|
|
.with_margin_top(4.)
|
|
.finish(),
|
|
);
|
|
}
|
|
}
|
|
|
|
ConstrainedBox::new(
|
|
Container::new(column.finish())
|
|
.with_padding_left(SIDECAR_PADDING)
|
|
.with_padding_right(SIDECAR_PADDING)
|
|
.with_padding_top(SIDECAR_PADDING)
|
|
.with_padding_bottom(SIDECAR_PADDING)
|
|
.with_background(theme.surface_2())
|
|
.with_corner_radius(CornerRadius::with_all(Radius::Pixels(8.)))
|
|
.with_border(Border::all(1.).with_border_fill(theme.outline()))
|
|
.finish(),
|
|
)
|
|
.with_width(SIDECAR_WIDTH)
|
|
.finish()
|
|
}
|