388 lines
14 KiB
Rust
388 lines
14 KiB
Rust
use galaxyui::{
|
|
elements::MouseStateHandle, AppContext, EntityId, SingletonEntity, ViewContext, ViewHandle,
|
|
WindowId,
|
|
};
|
|
use serde::{Deserialize, Serialize};
|
|
|
|
use super::OneTimeModalModel;
|
|
use crate::window_settings::WindowSettings;
|
|
use crate::{
|
|
appearance::Appearance, pane_group::PaneId, terminal::TerminalView, workspace::Workspace,
|
|
};
|
|
|
|
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
|
/// What composes a pane (i.e. the pane group and the pane itself).
|
|
pub struct PaneViewLocator {
|
|
pub pane_group_id: EntityId,
|
|
pub pane_id: PaneId,
|
|
}
|
|
|
|
#[derive(Default)]
|
|
#[allow(dead_code)]
|
|
pub(super) struct WorkspaceMouseStates {
|
|
pub(super) new_tab_button: MouseStateHandle,
|
|
pub(super) new_tab_menu: MouseStateHandle,
|
|
pub(super) new_tab: MouseStateHandle,
|
|
pub(super) overflow_button: MouseStateHandle,
|
|
pub(super) banner_button: MouseStateHandle,
|
|
pub(super) banner_secondary_button: MouseStateHandle,
|
|
pub(super) more_info_banner_button: MouseStateHandle,
|
|
pub(super) resource_center_icon: MouseStateHandle,
|
|
pub(super) ai_tab_bar_button: MouseStateHandle,
|
|
pub(super) agent_management_view_button: MouseStateHandle,
|
|
pub(super) left_panel_icon: MouseStateHandle,
|
|
pub(super) settings_icon: MouseStateHandle,
|
|
pub(super) dismiss_banner_button: MouseStateHandle,
|
|
pub(super) sign_in_button: MouseStateHandle,
|
|
pub(super) sign_up_button: MouseStateHandle,
|
|
pub(super) offline_icon: MouseStateHandle,
|
|
pub(super) avatar_icon: MouseStateHandle,
|
|
pub(super) header_dimming: MouseStateHandle,
|
|
pub(super) right_panel_icon: MouseStateHandle,
|
|
pub(super) notifications_mailbox: MouseStateHandle,
|
|
pub(super) session_config_tab_config_chip_close: MouseStateHandle,
|
|
pub(super) tools_panel_icon: MouseStateHandle,
|
|
pub(super) title_bar_search_bar: MouseStateHandle,
|
|
#[cfg(target_family = "wasm")]
|
|
pub(super) warp_logo: MouseStateHandle,
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
pub enum WelcomeTipsViewState {
|
|
Unavailable,
|
|
Available { is_popup_open: bool },
|
|
}
|
|
|
|
impl WelcomeTipsViewState {
|
|
pub fn is_popup_open(&self) -> bool {
|
|
matches!(
|
|
self,
|
|
WelcomeTipsViewState::Available {
|
|
is_popup_open: true,
|
|
..
|
|
}
|
|
)
|
|
}
|
|
|
|
pub fn close_popup(&mut self) {
|
|
if let WelcomeTipsViewState::Available {
|
|
ref mut is_popup_open,
|
|
..
|
|
} = self
|
|
{
|
|
*is_popup_open = false;
|
|
}
|
|
}
|
|
|
|
pub fn toggle_popup(&mut self) {
|
|
if let WelcomeTipsViewState::Available {
|
|
ref mut is_popup_open,
|
|
..
|
|
} = self
|
|
{
|
|
*is_popup_open = !*is_popup_open;
|
|
}
|
|
}
|
|
}
|
|
|
|
// TODO change this struct to enum (as we can only have 1 of them set to true at a time)
|
|
#[derive(Clone, Copy, Debug, Default)]
|
|
pub struct WorkspaceState {
|
|
pub is_palette_open: bool,
|
|
pub is_ctrl_tab_palette_open: bool,
|
|
pub is_theme_chooser_open: bool,
|
|
pub is_theme_creator_modal_open: bool,
|
|
pub is_theme_deletion_modal_open: bool,
|
|
pub is_changelog_modal_open: bool,
|
|
pub is_tab_being_dragged: bool,
|
|
pub is_reward_modal_open: bool,
|
|
pub is_launch_config_save_modal_open: bool,
|
|
pub is_resource_center_open: bool,
|
|
pub is_command_search_open: bool,
|
|
pub is_warp_drive_open: bool,
|
|
pub is_ai_assistant_panel_open: bool,
|
|
pub is_agent_management_popup_open: bool,
|
|
pub is_auth_override_modal_open: bool,
|
|
pub is_require_login_modal_open: bool,
|
|
pub is_workflow_modal_open: bool,
|
|
pub is_prompt_editor_open: bool,
|
|
pub is_agent_toolbar_editor_open: bool,
|
|
pub is_header_toolbar_editor_open: bool,
|
|
pub is_import_modal_open: bool,
|
|
pub is_close_session_confirmation_dialog_open: bool,
|
|
pub is_rewind_confirmation_dialog_open: bool,
|
|
pub is_delete_conversation_confirmation_dialog_open: bool,
|
|
pub is_native_quit_modal_open: bool,
|
|
pub is_shared_objects_creation_denied_modal_open: bool,
|
|
pub is_suggested_agent_mode_workflow_modal_open: bool,
|
|
pub is_suggested_rule_modal_open: bool,
|
|
pub is_enable_auto_reload_modal_open: bool,
|
|
pub is_notification_mailbox_open: bool,
|
|
pub is_agent_management_view_open: bool,
|
|
pub is_codex_modal_open: bool,
|
|
pub is_cloud_agent_capacity_modal_open: bool,
|
|
pub is_free_tier_limit_hit_modal_open: bool,
|
|
pub is_tab_config_params_modal_open: bool,
|
|
pub is_session_config_modal_open: bool,
|
|
pub is_new_worktree_modal_open: bool,
|
|
pub is_remove_tab_config_dialog_open: bool,
|
|
/// Whether the transcript details panel is open (WASM only, for conversation transcript viewing).
|
|
pub is_transcript_details_panel_open: bool,
|
|
tab_being_renamed: Option<usize>, // The index of the tab being renamed
|
|
pane_being_renamed: Option<PaneViewLocator>,
|
|
}
|
|
|
|
impl WorkspaceState {
|
|
pub fn is_any_non_terminal_view_open(&self, app: &AppContext) -> bool {
|
|
self.is_any_modal_open(app)
|
|
|| self.is_theme_chooser_open
|
|
|| self.is_ai_assistant_panel_open
|
|
|| self.is_workflow_modal_open
|
|
|| self.is_warp_drive_open
|
|
}
|
|
|
|
pub fn is_any_non_palette_modal_open(&self, app: &AppContext) -> bool {
|
|
self.is_theme_creator_modal_open
|
|
|| self.is_theme_deletion_modal_open
|
|
|| self.is_changelog_modal_open
|
|
|| self.tab_being_renamed.is_some()
|
|
|| self.pane_being_renamed.is_some()
|
|
|| self.is_reward_modal_open
|
|
|| self.is_launch_config_save_modal_open
|
|
|| self.is_command_search_open
|
|
|| self.is_prompt_editor_open
|
|
|| self.is_agent_toolbar_editor_open
|
|
|| self.is_header_toolbar_editor_open
|
|
|| self.is_agent_management_popup_open
|
|
|| self.is_import_modal_open
|
|
|| self.is_shared_objects_creation_denied_modal_open
|
|
|| self.is_suggested_rule_modal_open
|
|
|| self.is_suggested_agent_mode_workflow_modal_open
|
|
|| self.is_enable_auto_reload_modal_open
|
|
|| self.is_codex_modal_open
|
|
|| self.is_cloud_agent_capacity_modal_open
|
|
|| self.is_free_tier_limit_hit_modal_open
|
|
|| self.is_tab_config_params_modal_open
|
|
|| self.is_session_config_modal_open
|
|
|| self.is_new_worktree_modal_open
|
|
|| self.is_remove_tab_config_dialog_open
|
|
|| {
|
|
let one_time_modal = OneTimeModalModel::as_ref(app);
|
|
one_time_modal.is_oz_launch_modal_open()
|
|
|| one_time_modal.is_build_plan_migration_modal_open()
|
|
}
|
|
}
|
|
|
|
/// Returns whether any modal (sitting over terminal views) is open.
|
|
pub fn is_any_modal_open(&self, app: &AppContext) -> bool {
|
|
self.is_any_non_palette_modal_open(app)
|
|
|| self.is_palette_open
|
|
|| self.is_ctrl_tab_palette_open
|
|
}
|
|
|
|
pub fn close_all_modals(&mut self) {
|
|
self.is_palette_open = false;
|
|
self.is_ctrl_tab_palette_open = false;
|
|
self.is_theme_creator_modal_open = false;
|
|
self.is_theme_deletion_modal_open = false;
|
|
self.is_changelog_modal_open = false;
|
|
self.tab_being_renamed = None;
|
|
self.pane_being_renamed = None;
|
|
self.is_reward_modal_open = false;
|
|
self.is_launch_config_save_modal_open = false;
|
|
self.is_command_search_open = false;
|
|
self.is_workflow_modal_open = false;
|
|
self.is_prompt_editor_open = false;
|
|
self.is_agent_toolbar_editor_open = false;
|
|
self.is_header_toolbar_editor_open = false;
|
|
self.is_import_modal_open = false;
|
|
self.is_shared_objects_creation_denied_modal_open = false;
|
|
self.is_auth_override_modal_open = false;
|
|
self.is_require_login_modal_open = false;
|
|
self.is_suggested_rule_modal_open = false;
|
|
self.is_suggested_agent_mode_workflow_modal_open = false;
|
|
self.is_enable_auto_reload_modal_open = false;
|
|
self.is_codex_modal_open = false;
|
|
self.is_cloud_agent_capacity_modal_open = false;
|
|
self.is_free_tier_limit_hit_modal_open = false;
|
|
self.is_tab_config_params_modal_open = false;
|
|
self.is_session_config_modal_open = false;
|
|
self.is_new_worktree_modal_open = false;
|
|
self.is_remove_tab_config_dialog_open = false;
|
|
}
|
|
|
|
pub fn is_right_panel_open(&self) -> bool {
|
|
self.is_resource_center_open || self.is_ai_assistant_panel_open
|
|
}
|
|
|
|
pub fn is_left_panel_open(&self) -> bool {
|
|
self.is_theme_chooser_open
|
|
}
|
|
|
|
pub fn close_all_left_panels(&mut self) {
|
|
self.is_warp_drive_open = false;
|
|
self.is_theme_chooser_open = false;
|
|
}
|
|
|
|
pub fn is_tab_being_renamed(&self) -> bool {
|
|
self.tab_being_renamed.is_some()
|
|
}
|
|
|
|
pub fn set_tab_being_renamed(&mut self, index: usize) {
|
|
self.tab_being_renamed = Some(index);
|
|
self.pane_being_renamed = None;
|
|
}
|
|
|
|
pub fn clear_tab_being_renamed(&mut self) {
|
|
self.tab_being_renamed = None;
|
|
}
|
|
|
|
pub fn tab_being_renamed(&self) -> Option<usize> {
|
|
self.tab_being_renamed
|
|
}
|
|
|
|
pub fn is_pane_being_renamed(&self, pane: PaneViewLocator) -> bool {
|
|
self.pane_being_renamed == Some(pane)
|
|
}
|
|
|
|
pub fn is_any_pane_being_renamed(&self) -> bool {
|
|
self.pane_being_renamed.is_some()
|
|
}
|
|
|
|
pub fn set_pane_being_renamed(&mut self, pane: PaneViewLocator) {
|
|
self.pane_being_renamed = Some(pane);
|
|
self.tab_being_renamed = None;
|
|
}
|
|
|
|
pub fn clear_pane_being_renamed(&mut self) {
|
|
self.pane_being_renamed = None;
|
|
}
|
|
|
|
pub fn pane_being_renamed(&self) -> Option<PaneViewLocator> {
|
|
self.pane_being_renamed
|
|
}
|
|
}
|
|
|
|
/// Used to represent left and right movement for tabs in WorkspaceActions
|
|
#[derive(PartialEq, Eq, Clone, Serialize, Deserialize, Copy)]
|
|
pub enum TabMovement {
|
|
Left,
|
|
Right,
|
|
}
|
|
|
|
/// Fallback behavior for when a terminal input is needed, but none are available.
|
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
|
|
pub enum TerminalSessionFallbackBehavior {
|
|
/// Never open a new terminal session; fail if there is not an active session with no running
|
|
/// command.
|
|
RequireExisting,
|
|
/// Open a new terminal session if and only if there is no active session. If the active
|
|
/// session is busy, fail.
|
|
#[default]
|
|
OpenIfNone,
|
|
/// Open a new terminal session if there is active session OR if the active session is busy.
|
|
OpenIfNeeded,
|
|
}
|
|
|
|
/// Given a [`WindowId`], see if its [`Workspace`] contains an active [`TerminalView`] and return
|
|
/// that.
|
|
///
|
|
/// Note that "active" is not the same as "focused" in Warp's pane management.
|
|
pub fn active_terminal_in_window<T, F>(
|
|
window_id: WindowId,
|
|
ctx: &mut AppContext,
|
|
update: F,
|
|
) -> Option<T>
|
|
where
|
|
F: FnOnce(&mut TerminalView, &mut ViewContext<TerminalView>) -> T,
|
|
{
|
|
ctx.views_of_type::<Workspace>(window_id)
|
|
.as_ref()
|
|
.and_then(|v| v.first())
|
|
.and_then(|handle| {
|
|
handle.update(ctx, |workspace, w_ctx| {
|
|
workspace
|
|
.active_tab_pane_group()
|
|
.update(w_ctx, |active_group, a_ctx| {
|
|
active_group
|
|
.active_session_view(a_ctx)
|
|
.map(|terminal| terminal.update(a_ctx, update))
|
|
})
|
|
})
|
|
})
|
|
}
|
|
|
|
/// Check if two terminal views are in the same tab pane group.
|
|
///
|
|
/// Returns true if both terminal views are in the same tab, false otherwise.
|
|
pub fn is_terminal_view_in_same_tab(
|
|
terminal_view_id_1: &EntityId,
|
|
terminal_view_id_2: &EntityId,
|
|
app: &AppContext,
|
|
) -> bool {
|
|
if terminal_view_id_1 == terminal_view_id_2 {
|
|
return true;
|
|
}
|
|
let Some(active_window) = app.windows().active_window() else {
|
|
return false;
|
|
};
|
|
let Some(workspace) = app
|
|
.views_of_type::<Workspace>(active_window)
|
|
.and_then(|views| views.first().cloned())
|
|
else {
|
|
return false;
|
|
};
|
|
let workspace = workspace.as_ref(app);
|
|
workspace
|
|
.list_tab_pane_groups(app)
|
|
.into_iter()
|
|
.any(|tab_pane_group| {
|
|
tab_pane_group.terminal_ids.contains(terminal_view_id_1)
|
|
&& tab_pane_group.terminal_ids.contains(terminal_view_id_2)
|
|
})
|
|
}
|
|
|
|
/// Returns the active terminal session view in the active tab. This is used as the target for any
|
|
/// selections in the adjacent editor.
|
|
pub fn get_context_target_terminal_view(
|
|
window_id: WindowId,
|
|
ctx: &AppContext,
|
|
) -> Option<ViewHandle<TerminalView>> {
|
|
ctx.views_of_type::<Workspace>(window_id)
|
|
.as_ref()
|
|
.and_then(|v| v.first())
|
|
.and_then(|handle| {
|
|
handle.read(ctx, |workspace, w_ctx| {
|
|
workspace
|
|
.active_tab_pane_group()
|
|
.read(w_ctx, |active_group, a_ctx| {
|
|
active_group.active_session_view(a_ctx)
|
|
})
|
|
})
|
|
})
|
|
}
|
|
|
|
pub fn get_terminal_background_fill(
|
|
window_id: WindowId,
|
|
app: &AppContext,
|
|
) -> galaxyui::elements::Fill {
|
|
let theme = Appearance::as_ref(app).theme();
|
|
let terminal_opacity = get_terminal_background_opacity(window_id, app);
|
|
theme.background().with_opacity(terminal_opacity).into()
|
|
}
|
|
|
|
fn get_terminal_background_opacity(window_id: WindowId, app: &AppContext) -> u8 {
|
|
let theme = Appearance::as_ref(app).theme();
|
|
let background_opacity = WindowSettings::as_ref(app)
|
|
.background_opacity
|
|
.effective_opacity(window_id, app);
|
|
|
|
if let Some(img) = theme.background_image() {
|
|
let opacity_ratio = background_opacity as f32 / 100.;
|
|
// Scale the overlay opacity with the background opacity ratio.
|
|
(((100 - img.opacity) as f32) * opacity_ratio) as u8
|
|
} else {
|
|
background_opacity
|
|
}
|
|
}
|