mirror of
https://github.com/nyakang/nyaterm.git
synced 2026-10-09 00:01:10 +00:00
refactor(shell): own window interaction state
This commit is contained in:
@@ -1,9 +1,9 @@
|
||||
use crate::models::{
|
||||
ActivityBarLayoutState, BottomPanelMode, HeaderStatusState, MainMode, NavItem, PanelSide,
|
||||
RightFocus, SessionEventBridge, SettingsTab, StoreStatus, TerminalFramePipeline,
|
||||
ActivityBarLayoutState, BottomPanelMode, NavItem, PanelSide, SessionEventBridge, StoreStatus,
|
||||
TerminalFramePipeline,
|
||||
};
|
||||
use crate::terminal::initial_terminal_screen;
|
||||
use gpui::{Context, ScrollHandle};
|
||||
use gpui::Context;
|
||||
use nyaterm_core::{
|
||||
AiSettings, AppRuntime, AppSettingsSummary, CLOUD_SYNC_HISTORY_LIMIT, CloudSyncSettings,
|
||||
CloudSyncState, ConnectionStore, KeywordHighlightConfig, NativeServices, TranslationSettings,
|
||||
@@ -11,7 +11,7 @@ use nyaterm_core::{
|
||||
};
|
||||
use nyaterm_terminal::TerminalOutputDecoder;
|
||||
use nyaterm_transport::{SessionManager, SftpDuplicatePolicy};
|
||||
use std::collections::{HashMap, HashSet, VecDeque};
|
||||
use std::collections::{HashMap, VecDeque};
|
||||
use std::sync::Arc;
|
||||
|
||||
use super::super::settings::{SettingsFeatureFocus, SettingsFeatureState};
|
||||
@@ -21,10 +21,10 @@ use super::super::{
|
||||
QuickCommandFeatureFocus, QuickCommandFeatureState, RecordingFeatureState,
|
||||
RemoteOpsFeatureFocus, RemoteOpsFeatureState, SecurityFeatureFocus, SecurityFeatureState,
|
||||
SendCommandFeatureFocus, SendCommandFeatureState, SessionFeatureFocus, SessionFeatureState,
|
||||
ShellFeatureState, SyncInputFeatureState, TerminalFeatureFocus, TerminalFeatureState,
|
||||
TextInputRegistry, TransferFeatureFocus, TransferFeatureState, TranslationFeatureState,
|
||||
TunnelFeatureState, UpdateFeatureState, ai_active_profile_drafts, ai_usage_counts,
|
||||
appearance_font_options, quick_command_sort_mode_from_setting,
|
||||
ShellFeatureInit, ShellFeatureState, SyncInputFeatureState, TerminalFeatureFocus,
|
||||
TerminalFeatureState, TextInputRegistry, TransferFeatureFocus, TransferFeatureState,
|
||||
TranslationFeatureState, TunnelFeatureState, UpdateFeatureState, ai_active_profile_drafts,
|
||||
ai_usage_counts, appearance_font_options, quick_command_sort_mode_from_setting,
|
||||
quick_command_view_mode_from_setting, spawn_command_persistence_worker,
|
||||
};
|
||||
use super::NyaTermApp;
|
||||
@@ -444,17 +444,28 @@ impl NyaTermApp {
|
||||
},
|
||||
),
|
||||
text_inputs: TextInputRegistry::default(),
|
||||
shell: ShellFeatureState::new(
|
||||
if settings.ui_serial_send_visible {
|
||||
shell: ShellFeatureState::new(ShellFeatureInit {
|
||||
bottom_panel_mode: if settings.ui_serial_send_visible {
|
||||
BottomPanelMode::CommandSend
|
||||
} else if settings.ui_quick_cmd_visible {
|
||||
BottomPanelMode::QuickCommands
|
||||
} else {
|
||||
BottomPanelMode::Hidden
|
||||
},
|
||||
quick_cmd_height,
|
||||
serial_send_height,
|
||||
),
|
||||
quick_commands_height: quick_cmd_height,
|
||||
command_send_height: serial_send_height,
|
||||
active_left_panel,
|
||||
active_right_panel,
|
||||
left_open_panels,
|
||||
right_open_panels,
|
||||
panel_stack_sizes,
|
||||
panel_multi_open,
|
||||
left_sidebar_collapsed,
|
||||
right_inspector_collapsed,
|
||||
left_panel_width,
|
||||
right_panel_width,
|
||||
activity_bar_layout,
|
||||
}),
|
||||
sync_input: SyncInputFeatureState::new(cx.focus_handle()),
|
||||
keyword_highlights,
|
||||
settings,
|
||||
@@ -472,53 +483,6 @@ impl NyaTermApp {
|
||||
active_snapshot_password_prompt: None,
|
||||
pending_session_events: VecDeque::new(),
|
||||
diagnostic_log_last_at: HashMap::new(),
|
||||
last_viewport_size: (1280., 800.),
|
||||
wallpaper_tile_dimensions: None,
|
||||
last_viewport_change_at: None,
|
||||
title_drag_active_until: None,
|
||||
selected_nav: NavItem::Workspace,
|
||||
main_mode: MainMode::Workspace,
|
||||
settings_active_tab: SettingsTab::General,
|
||||
settings_expanded_groups: HashSet::from(["workspace".to_string()]),
|
||||
settings_draft_snapshot: None,
|
||||
settings_window: None,
|
||||
settings_window_open_pending: false,
|
||||
settings_previous_left_collapsed: None,
|
||||
settings_previous_right_collapsed: None,
|
||||
active_left_panel,
|
||||
active_right_panel,
|
||||
left_open_panels,
|
||||
right_open_panels,
|
||||
panel_stack_sizes,
|
||||
panel_multi_open,
|
||||
right_focus: RightFocus::Default,
|
||||
left_sidebar_collapsed,
|
||||
right_inspector_collapsed,
|
||||
mobile_left_open: false,
|
||||
mobile_right_open: false,
|
||||
left_panel_width,
|
||||
right_panel_width,
|
||||
panel_resize: None,
|
||||
panel_stack_resize: None,
|
||||
activity_bar_layout,
|
||||
activity_bar_context_menu: None,
|
||||
title_menu_open: None,
|
||||
title_menu_submenu: None,
|
||||
header_status: HeaderStatusState::default(),
|
||||
open_tabs_menu_open: false,
|
||||
new_session_menu_open: false,
|
||||
new_session_all_sessions_open: false,
|
||||
new_session_group_menu_path: Vec::new(),
|
||||
session_tab_strip_scroll: ScrollHandle::new(),
|
||||
session_tab_scroll_into_view_pending: false,
|
||||
last_connect_failure_name: None,
|
||||
last_connect_failure_error: None,
|
||||
workspace_split: None,
|
||||
workspace_split_resize: None,
|
||||
session_pane_roots: HashMap::new(),
|
||||
session_tab_owner: HashMap::new(),
|
||||
focused_terminal_window_leaf_id: None,
|
||||
workspace_pane_layout_restored: false,
|
||||
startup_restore_complete: false,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
use std::collections::{HashMap, HashSet, VecDeque};
|
||||
use std::collections::{HashMap, VecDeque};
|
||||
use std::sync::{Arc, mpsc};
|
||||
use std::time::Instant;
|
||||
|
||||
use gpui::{ScrollHandle, WindowHandle};
|
||||
use gpui::WindowHandle;
|
||||
use nyaterm_core::{
|
||||
AppRuntime, AppSettingsSummary, CommandHistoryEntry, Group, KeywordHighlightConfig,
|
||||
NativeServices, OtpEntry, ProxyConfig, ProxyGroup, QuickCommand, QuickCommandCategory,
|
||||
@@ -21,7 +21,6 @@ use super::remote_editor_window::RemoteFileEditorWindow;
|
||||
use super::runtime_jobs::{CommandPersistenceRequest, CommandPersistenceResult};
|
||||
use super::session::SessionFeatureState;
|
||||
use super::settings::{SecurityFeatureState, SettingsFeatureState};
|
||||
use super::settings_window::SettingsWindow;
|
||||
use super::shell::ShellFeatureState;
|
||||
use super::sync::CloudSyncFeatureState;
|
||||
use super::sync_input::SyncInputFeatureState;
|
||||
@@ -32,11 +31,8 @@ use super::translation::TranslationFeatureState;
|
||||
use super::tunnels::TunnelFeatureState;
|
||||
use super::update::UpdateFeatureState;
|
||||
use crate::models::{
|
||||
ActivityBarContextMenuState, ActivityBarLayoutState, ConfigPathPromptKind,
|
||||
DiagnosticsPathPromptKind, HeaderStatusState, KeywordHighlightPathPromptKind, MainMode,
|
||||
NavItem, PanelResizeState, PanelStackResizeState, RightFocus, SettingsTab,
|
||||
SnapshotPasswordPromptState, StoreStatus, TitleMenu, TitleMenuSubmenu, WorkspacePaneNode,
|
||||
WorkspaceSplitResizeState, WorkspaceSplitState,
|
||||
ConfigPathPromptKind, DiagnosticsPathPromptKind, KeywordHighlightPathPromptKind,
|
||||
SnapshotPasswordPromptState, StoreStatus,
|
||||
};
|
||||
|
||||
mod construct;
|
||||
@@ -104,70 +100,5 @@ pub struct NyaTermApp {
|
||||
pub(in crate::features) active_snapshot_password_prompt: Option<SnapshotPasswordPromptState>,
|
||||
pub(in crate::features) pending_session_events: VecDeque<SessionEvent>,
|
||||
pub(in crate::features) diagnostic_log_last_at: HashMap<&'static str, Instant>,
|
||||
pub(in crate::features) last_viewport_size: (f32, f32),
|
||||
/// Cached intrinsic dimensions for the current tiled wallpaper path.
|
||||
pub(in crate::features) wallpaper_tile_dimensions: Option<(String, u32, u32)>,
|
||||
/// When the window viewport last changed (resize/drag geometry).
|
||||
pub(in crate::features) last_viewport_change_at: Option<Instant>,
|
||||
/// Deadline for treating title-bar window dragging as active.
|
||||
pub(in crate::features) title_drag_active_until: Option<Instant>,
|
||||
pub(in crate::features) selected_nav: NavItem,
|
||||
pub(in crate::features) main_mode: MainMode,
|
||||
pub(in crate::features) settings_active_tab: SettingsTab,
|
||||
/// Expanded multi-item groups in the settings sidebar (Tauri keeps this local to the page).
|
||||
pub(in crate::features) settings_expanded_groups: HashSet<String>,
|
||||
/// Committed values captured when the in-window settings page opens.
|
||||
pub(in crate::features) settings_draft_snapshot: Option<SettingsDraftSnapshot>,
|
||||
pub(in crate::features) settings_window: Option<WindowHandle<SettingsWindow>>,
|
||||
pub(in crate::features) settings_window_open_pending: bool,
|
||||
/// Main workspace panel state to restore after leaving the in-window settings page.
|
||||
pub(in crate::features) settings_previous_left_collapsed: Option<bool>,
|
||||
pub(in crate::features) settings_previous_right_collapsed: Option<bool>,
|
||||
pub(in crate::features) active_left_panel: Option<NavItem>,
|
||||
pub(in crate::features) active_right_panel: Option<NavItem>,
|
||||
pub(in crate::features) left_open_panels: Vec<String>,
|
||||
pub(in crate::features) right_open_panels: Vec<String>,
|
||||
pub(in crate::features) panel_stack_sizes: HashMap<String, f32>,
|
||||
pub(in crate::features) panel_multi_open: bool,
|
||||
pub(in crate::features) right_focus: RightFocus,
|
||||
pub(in crate::features) left_sidebar_collapsed: bool,
|
||||
pub(in crate::features) right_inspector_collapsed: bool,
|
||||
pub(in crate::features) mobile_left_open: bool,
|
||||
pub(in crate::features) mobile_right_open: bool,
|
||||
pub(in crate::features) left_panel_width: f32,
|
||||
pub(in crate::features) right_panel_width: f32,
|
||||
pub(in crate::features) panel_resize: Option<PanelResizeState>,
|
||||
pub(in crate::features) panel_stack_resize: Option<PanelStackResizeState>,
|
||||
pub(in crate::features) activity_bar_layout: ActivityBarLayoutState,
|
||||
pub(in crate::features) activity_bar_context_menu: Option<ActivityBarContextMenuState>,
|
||||
pub(in crate::features) title_menu_open: Option<TitleMenu>,
|
||||
pub(in crate::features) title_menu_submenu: Option<TitleMenuSubmenu>,
|
||||
pub(in crate::features) header_status: HeaderStatusState,
|
||||
/// Open-tabs overflow menu (Tauri TabBar expand-more when many tabs).
|
||||
pub(in crate::features) open_tabs_menu_open: bool,
|
||||
/// New-session menu next to the tab strip + control.
|
||||
pub(in crate::features) new_session_menu_open: bool,
|
||||
/// Whether the Tauri-style "All sessions" submenu is expanded.
|
||||
pub(in crate::features) new_session_all_sessions_open: bool,
|
||||
/// Hovered group ids from the root submenu through the deepest open child menu.
|
||||
pub(in crate::features) new_session_group_menu_path: Vec<String>,
|
||||
/// Horizontal scroll handle for the global session tab strip (scroll-into-view).
|
||||
pub(in crate::features) session_tab_strip_scroll: ScrollHandle,
|
||||
/// Request scroll-into-view of the active tab on next paint (Tauri TabBar).
|
||||
pub(in crate::features) session_tab_scroll_into_view_pending: bool,
|
||||
/// Last failed connect name (shown as ephemeral failed tab chrome).
|
||||
pub(in crate::features) last_connect_failure_name: Option<String>,
|
||||
/// Last failed connect error text.
|
||||
pub(in crate::features) last_connect_failure_error: Option<String>,
|
||||
/// Legacy/global active pane tree view: mirrors the active tab's per-tab root when split.
|
||||
pub(in crate::features) workspace_split: Option<WorkspaceSplitState>,
|
||||
pub(in crate::features) workspace_split_resize: Option<WorkspaceSplitResizeState>,
|
||||
/// Per-tab pane trees keyed by tab-root session id (Tauri `Tab.root`).
|
||||
pub(in crate::features) session_pane_roots: HashMap<String, WorkspacePaneNode>,
|
||||
/// Leaf session id → owning tab-root session id (hidden from tab strip when secondary).
|
||||
pub(in crate::features) session_tab_owner: HashMap<String, String>,
|
||||
pub(in crate::features) focused_terminal_window_leaf_id: Option<String>,
|
||||
/// Whether we already attempted startup restore of global workspace pane splits.
|
||||
pub(in crate::features) workspace_pane_layout_restored: bool,
|
||||
pub(in crate::features) startup_restore_complete: bool,
|
||||
}
|
||||
|
||||
@@ -1154,7 +1154,7 @@ impl NyaTermApp {
|
||||
insets.left,
|
||||
insets.top,
|
||||
self.terminal_gutter_width_px_for_session(session_id),
|
||||
self.last_viewport_size,
|
||||
self.shell.viewport.size,
|
||||
cursor_row,
|
||||
cursor_col,
|
||||
menu_w,
|
||||
|
||||
@@ -47,8 +47,8 @@ impl NyaTermApp {
|
||||
|
||||
self.connection_state.open_import_dialog();
|
||||
self.connection_state.close_list_more_menu();
|
||||
self.title_menu_open = None;
|
||||
self.title_menu_submenu = None;
|
||||
self.shell.chrome.title_menu_open = None;
|
||||
self.shell.chrome.title_menu_submenu = None;
|
||||
self.terminal.view.status = "select a connection import source".to_string();
|
||||
let import_focus = self.connection_state.import_focus_handle();
|
||||
window.focus(&import_focus);
|
||||
|
||||
@@ -185,8 +185,8 @@ impl NyaTermApp {
|
||||
) -> bool {
|
||||
if self.saved_connection_start_is_pending_or_queued(&connection) {
|
||||
self.terminal.view.status = format!("{} is already queued", connection.name);
|
||||
self.selected_nav = NavItem::Workspace;
|
||||
self.main_mode = MainMode::Workspace;
|
||||
self.shell.navigation.selected_nav = NavItem::Workspace;
|
||||
self.shell.navigation.main_mode = MainMode::Workspace;
|
||||
cx.notify();
|
||||
return false;
|
||||
}
|
||||
@@ -200,8 +200,8 @@ impl NyaTermApp {
|
||||
"queued {name} ({} pending)",
|
||||
self.pending_saved_connection_queue.len()
|
||||
);
|
||||
self.selected_nav = NavItem::Workspace;
|
||||
self.main_mode = MainMode::Workspace;
|
||||
self.shell.navigation.selected_nav = NavItem::Workspace;
|
||||
self.shell.navigation.main_mode = MainMode::Workspace;
|
||||
cx.notify();
|
||||
true
|
||||
}
|
||||
|
||||
@@ -687,7 +687,7 @@ impl NyaTermApp {
|
||||
|this, _, _, cx| {
|
||||
this.ai.discovery.menu_open =
|
||||
false;
|
||||
this.settings_active_tab =
|
||||
this.shell.navigation.settings.active_tab =
|
||||
SettingsTab::AiModels;
|
||||
this.open_page(
|
||||
NavItem::Settings,
|
||||
|
||||
@@ -86,7 +86,7 @@ impl NyaTermApp {
|
||||
});
|
||||
let quote_text = state.text.clone();
|
||||
let copy_text = state.text.clone();
|
||||
let (viewport_w, viewport_h) = self.last_viewport_size;
|
||||
let (viewport_w, viewport_h) = self.shell.viewport.size;
|
||||
let (menu_x, menu_y, menu_max_h) = ai_message_menu_position(
|
||||
f32::from(state.x),
|
||||
f32::from(state.y),
|
||||
|
||||
@@ -112,7 +112,7 @@ impl NyaTermApp {
|
||||
.cursor_pointer()
|
||||
.hover(|this| this.bg(rgb(0x2ea043)))
|
||||
.on_click(cx.listener(|this, _, _, cx| {
|
||||
this.settings_active_tab = SettingsTab::AiGeneral;
|
||||
this.shell.navigation.settings.active_tab = SettingsTab::AiGeneral;
|
||||
this.open_page(NavItem::Settings, cx);
|
||||
}))
|
||||
.child(self.tr("ai.setupAction")),
|
||||
|
||||
@@ -9,9 +9,9 @@ impl NyaTermApp {
|
||||
window: &mut gpui::Window,
|
||||
cx: &mut Context<Self>,
|
||||
) -> impl IntoElement {
|
||||
let mut width = self.right_panel_width.clamp(200., 720.);
|
||||
if !cfg!(target_os = "macos") && self.last_viewport_size.0 < 768. {
|
||||
width = width.min((self.last_viewport_size.0 - 80.).max(120.));
|
||||
let mut width = self.shell.panels.right_width.clamp(200., 720.);
|
||||
if !cfg!(target_os = "macos") && self.shell.viewport.size.0 < 768. {
|
||||
width = width.min((self.shell.viewport.size.0 - 80.).max(120.));
|
||||
}
|
||||
let palette = self.theme_palette();
|
||||
div()
|
||||
|
||||
@@ -15,12 +15,12 @@ impl NyaTermApp {
|
||||
cx: &mut Context<Self>,
|
||||
) -> impl IntoElement {
|
||||
let palette = self.theme_palette();
|
||||
let Some(menu) = self.activity_bar_context_menu.clone() else {
|
||||
let Some(menu) = self.shell.chrome.activity_bar_context_menu.clone() else {
|
||||
return div().into_any_element();
|
||||
};
|
||||
let entry_id = menu.entry_id.clone();
|
||||
let show_labels = self.activity_bar_layout.show_labels;
|
||||
let (viewport_w, viewport_h) = self.last_viewport_size;
|
||||
let show_labels = self.shell.chrome.activity_bar_layout.show_labels;
|
||||
let (viewport_w, viewport_h) = self.shell.viewport.size;
|
||||
let menu_w = 180.;
|
||||
let submenu_w = 164.;
|
||||
let margin = 8.;
|
||||
@@ -191,7 +191,7 @@ impl NyaTermApp {
|
||||
let bottom_entries = self.activity_entries_for_zone(bottom_zone);
|
||||
let top_len = top_entries.len();
|
||||
let bottom_len = bottom_entries.len();
|
||||
let show_labels = self.activity_bar_layout.show_labels;
|
||||
let show_labels = self.shell.chrome.activity_bar_layout.show_labels;
|
||||
let palette = self.theme_palette();
|
||||
|
||||
// Tauri DropZone: gap-0.5 pt-1
|
||||
|
||||
@@ -67,7 +67,7 @@ impl NyaTermApp {
|
||||
.child(
|
||||
div()
|
||||
.id("duplicate-prompt-dialog")
|
||||
.w(px((self.last_viewport_size.0 - 32.).min(448.).max(280.)))
|
||||
.w(px((self.shell.viewport.size.0 - 32.).min(448.).max(280.)))
|
||||
.rounded_md()
|
||||
.border_1()
|
||||
.border_color(rgb(palette.border))
|
||||
@@ -616,7 +616,7 @@ impl NyaTermApp {
|
||||
prompt.id
|
||||
)))
|
||||
.w_full()
|
||||
.max_h(px((self.last_viewport_size.1 - 32.).max(240.)))
|
||||
.max_h(px((self.shell.viewport.size.1 - 32.).max(240.)))
|
||||
.overflow_y_scroll()
|
||||
.rounded_md()
|
||||
.border_1()
|
||||
|
||||
@@ -9,9 +9,9 @@ impl NyaTermApp {
|
||||
window: &mut gpui::Window,
|
||||
cx: &mut Context<Self>,
|
||||
) -> impl IntoElement {
|
||||
let mut width = self.left_panel_width.clamp(160., 720.);
|
||||
if !cfg!(target_os = "macos") && self.last_viewport_size.0 < 1024. {
|
||||
width = width.min((self.last_viewport_size.0 - 80.).max(120.));
|
||||
let mut width = self.shell.panels.left_width.clamp(160., 720.);
|
||||
if !cfg!(target_os = "macos") && self.shell.viewport.size.0 < 1024. {
|
||||
width = width.min((self.shell.viewport.size.0 - 80.).max(120.));
|
||||
}
|
||||
let palette = self.theme_palette();
|
||||
div()
|
||||
|
||||
@@ -28,8 +28,8 @@ impl NyaTermApp {
|
||||
let palette = self.theme_palette();
|
||||
let macos = cfg!(target_os = "macos");
|
||||
let compact_layout = !cfg!(target_os = "macos");
|
||||
let narrow_left = compact_layout && self.last_viewport_size.0 < 1024.;
|
||||
let narrow_right = compact_layout && self.last_viewport_size.0 < 768.;
|
||||
let narrow_left = compact_layout && self.shell.viewport.size.0 < 1024.;
|
||||
let narrow_right = compact_layout && self.shell.viewport.size.0 < 768.;
|
||||
let header_status_visible = self.settings.ui_header_status_visible;
|
||||
let header_status = self.header_status_content();
|
||||
// Match Tauri Header: h-10.
|
||||
@@ -207,7 +207,7 @@ impl NyaTermApp {
|
||||
cx: &mut Context<Self>,
|
||||
) -> impl IntoElement {
|
||||
let palette = self.theme_palette();
|
||||
let menu_open = self.header_status.menu_open;
|
||||
let menu_open = self.shell.chrome.header_status.menu_open;
|
||||
let select_label = self.tr("headerStatus.select");
|
||||
|
||||
div()
|
||||
@@ -406,14 +406,14 @@ impl NyaTermApp {
|
||||
}
|
||||
|
||||
pub(in crate::features) fn toggle_header_status_menu(&mut self, cx: &mut Context<Self>) {
|
||||
self.header_status.menu_open = !self.header_status.menu_open;
|
||||
if self.header_status.menu_open {
|
||||
self.title_menu_open = None;
|
||||
self.title_menu_submenu = None;
|
||||
self.open_tabs_menu_open = false;
|
||||
self.new_session_menu_open = false;
|
||||
self.new_session_all_sessions_open = false;
|
||||
self.new_session_group_menu_path.clear();
|
||||
self.shell.chrome.header_status.menu_open = !self.shell.chrome.header_status.menu_open;
|
||||
if self.shell.chrome.header_status.menu_open {
|
||||
self.shell.chrome.title_menu_open = None;
|
||||
self.shell.chrome.title_menu_submenu = None;
|
||||
self.shell.chrome.open_tabs_menu_open = false;
|
||||
self.shell.chrome.new_session_menu_open = false;
|
||||
self.shell.chrome.new_session_all_sessions_open = false;
|
||||
self.shell.chrome.new_session_group_menu_path.clear();
|
||||
}
|
||||
cx.notify();
|
||||
}
|
||||
@@ -425,8 +425,8 @@ impl NyaTermApp {
|
||||
) {
|
||||
self.settings.ui_header_status_mode = mode.persistence_id().to_string();
|
||||
self.settings.ui_header_status_visible = true;
|
||||
self.header_status.menu_open = false;
|
||||
self.header_status.rendered_minute = current_unix_minute();
|
||||
self.shell.chrome.header_status.menu_open = false;
|
||||
self.shell.chrome.header_status.rendered_minute = current_unix_minute();
|
||||
self.persist_header_status_settings();
|
||||
cx.notify();
|
||||
}
|
||||
@@ -437,13 +437,13 @@ impl NyaTermApp {
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
self.settings.ui_header_status_visible = visible;
|
||||
self.header_status.menu_open = false;
|
||||
self.shell.chrome.header_status.menu_open = false;
|
||||
self.persist_header_status_settings();
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
fn persist_header_status_settings(&mut self) {
|
||||
if self.settings_draft_snapshot.is_some() {
|
||||
if self.shell.navigation.settings.draft_snapshot.is_some() {
|
||||
self.terminal.view.status =
|
||||
"header status changed; apply settings to persist".to_string();
|
||||
} else {
|
||||
@@ -461,14 +461,14 @@ impl NyaTermApp {
|
||||
self.settings.ui_header_status_visible
|
||||
&& HeaderStatusMode::from_setting(&self.settings.ui_header_status_mode)
|
||||
== HeaderStatusMode::DateTime
|
||||
&& self.header_status.rendered_minute != current_unix_minute()
|
||||
&& self.shell.chrome.header_status.rendered_minute != current_unix_minute()
|
||||
}
|
||||
|
||||
pub(in crate::features) fn refresh_header_status_clock(&mut self) -> bool {
|
||||
if !self.header_status_clock_refresh_due() {
|
||||
return false;
|
||||
}
|
||||
self.header_status.rendered_minute = current_unix_minute();
|
||||
self.shell.chrome.header_status.rendered_minute = current_unix_minute();
|
||||
true
|
||||
}
|
||||
|
||||
@@ -509,7 +509,7 @@ impl NyaTermApp {
|
||||
if let Some(failed) = self.failed_session_display_name() {
|
||||
return failed;
|
||||
}
|
||||
if let Some(failed) = self.last_connect_failure_name.as_ref() {
|
||||
if let Some(failed) = self.shell.chrome.last_connect_failure_name.as_ref() {
|
||||
return failed.clone();
|
||||
}
|
||||
"NyaTerm".to_string()
|
||||
@@ -530,7 +530,8 @@ impl NyaTermApp {
|
||||
if self.has_pending_session_start() {
|
||||
return Some("icons/conn/connect.svg");
|
||||
}
|
||||
if self.has_failed_session_start() || self.last_connect_failure_name.is_some() {
|
||||
if self.has_failed_session_start() || self.shell.chrome.last_connect_failure_name.is_some()
|
||||
{
|
||||
return Some("icons/session/disconnect.svg");
|
||||
}
|
||||
None
|
||||
@@ -541,7 +542,7 @@ impl NyaTermApp {
|
||||
menu: TitleMenu,
|
||||
cx: &mut Context<Self>,
|
||||
) -> impl IntoElement {
|
||||
let open = self.title_menu_open == Some(menu);
|
||||
let open = self.shell.chrome.title_menu_open == Some(menu);
|
||||
let id_label = menu.label();
|
||||
let label = self.tr(menu.i18n_key());
|
||||
let palette = self.theme_palette();
|
||||
@@ -585,25 +586,25 @@ impl NyaTermApp {
|
||||
menu: TitleMenu,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
self.title_menu_open = if self.title_menu_open == Some(menu) {
|
||||
self.shell.chrome.title_menu_open = if self.shell.chrome.title_menu_open == Some(menu) {
|
||||
None
|
||||
} else {
|
||||
Some(menu)
|
||||
};
|
||||
self.title_menu_submenu = None;
|
||||
if self.title_menu_open.is_some() {
|
||||
self.header_status.menu_open = false;
|
||||
self.open_tabs_menu_open = false;
|
||||
self.new_session_menu_open = false;
|
||||
self.new_session_all_sessions_open = false;
|
||||
self.new_session_group_menu_path.clear();
|
||||
self.shell.chrome.title_menu_submenu = None;
|
||||
if self.shell.chrome.title_menu_open.is_some() {
|
||||
self.shell.chrome.header_status.menu_open = false;
|
||||
self.shell.chrome.open_tabs_menu_open = false;
|
||||
self.shell.chrome.new_session_menu_open = false;
|
||||
self.shell.chrome.new_session_all_sessions_open = false;
|
||||
self.shell.chrome.new_session_group_menu_path.clear();
|
||||
}
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
pub(in crate::features) fn close_title_menu(&mut self, cx: &mut Context<Self>) {
|
||||
self.title_menu_submenu = None;
|
||||
if self.title_menu_open.take().is_some() {
|
||||
self.shell.chrome.title_menu_submenu = None;
|
||||
if self.shell.chrome.title_menu_open.take().is_some() {
|
||||
cx.notify();
|
||||
}
|
||||
}
|
||||
@@ -613,8 +614,8 @@ impl NyaTermApp {
|
||||
submenu: TitleMenuSubmenu,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
if self.title_menu_submenu != Some(submenu) {
|
||||
self.title_menu_submenu = Some(submenu);
|
||||
if self.shell.chrome.title_menu_submenu != Some(submenu) {
|
||||
self.shell.chrome.title_menu_submenu = Some(submenu);
|
||||
cx.notify();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -43,10 +43,10 @@ impl NyaTermApp {
|
||||
Some(shortcut("tab.newSession", "Ctrl+Shift+N")),
|
||||
cx.listener(|this, _, window, cx| {
|
||||
this.close_title_menu(cx);
|
||||
this.open_tabs_menu_open = false;
|
||||
this.new_session_menu_open = false;
|
||||
this.new_session_all_sessions_open = false;
|
||||
this.new_session_group_menu_path.clear();
|
||||
this.shell.chrome.open_tabs_menu_open = false;
|
||||
this.shell.chrome.new_session_menu_open = false;
|
||||
this.shell.chrome.new_session_all_sessions_open = false;
|
||||
this.shell.chrome.new_session_group_menu_path.clear();
|
||||
this.open_connection_editor(None, None, false, window, cx);
|
||||
}),
|
||||
))
|
||||
@@ -266,7 +266,7 @@ impl NyaTermApp {
|
||||
}
|
||||
}
|
||||
|
||||
items.when_some(self.title_menu_submenu, |this, submenu| {
|
||||
items.when_some(self.shell.chrome.title_menu_submenu, |this, submenu| {
|
||||
this.child(self.title_menu_submenu(submenu, cx))
|
||||
})
|
||||
}
|
||||
@@ -280,7 +280,7 @@ impl NyaTermApp {
|
||||
cx: &mut Context<Self>,
|
||||
) -> impl IntoElement {
|
||||
let palette = self.theme_palette();
|
||||
let open = self.title_menu_submenu == Some(submenu);
|
||||
let open = self.shell.chrome.title_menu_submenu == Some(submenu);
|
||||
title_menu_submenu_trigger(
|
||||
palette,
|
||||
id,
|
||||
@@ -293,10 +293,10 @@ impl NyaTermApp {
|
||||
}
|
||||
}),
|
||||
cx.listener(move |this, _, _, cx| {
|
||||
if this.title_menu_submenu == Some(submenu) {
|
||||
this.title_menu_submenu = None;
|
||||
if this.shell.chrome.title_menu_submenu == Some(submenu) {
|
||||
this.shell.chrome.title_menu_submenu = None;
|
||||
} else {
|
||||
this.title_menu_submenu = Some(submenu);
|
||||
this.shell.chrome.title_menu_submenu = Some(submenu);
|
||||
}
|
||||
cx.notify();
|
||||
}),
|
||||
|
||||
@@ -151,7 +151,7 @@ impl NyaTermApp {
|
||||
let error = self
|
||||
.active_failed_session()
|
||||
.map(|failed| failed.error.clone())
|
||||
.or_else(|| self.last_connect_failure_error.clone())
|
||||
.or_else(|| self.shell.chrome.last_connect_failure_error.clone())
|
||||
.unwrap_or_default();
|
||||
|
||||
div()
|
||||
|
||||
@@ -165,7 +165,9 @@ impl NyaTermApp {
|
||||
.as_deref()
|
||||
.is_some_and(|id| self.tab_root_for_session(id) == session_id);
|
||||
let leaf_ids = self
|
||||
.session_pane_roots
|
||||
.shell
|
||||
.workspace
|
||||
.pane_roots
|
||||
.get(&session_id)
|
||||
.map(|root| root.session_ids())
|
||||
.unwrap_or_else(|| vec![session_id.clone()]);
|
||||
@@ -282,7 +284,7 @@ impl NyaTermApp {
|
||||
let no_shell_sessions_label = self.tr("terminal.noShellSessions");
|
||||
let recent_sessions_label = self.tr("terminal.recentSessions");
|
||||
let no_recent_sessions_label = self.tr("terminal.noRecentSessions");
|
||||
let all_sessions_open = self.new_session_all_sessions_open;
|
||||
let all_sessions_open = self.shell.chrome.new_session_all_sessions_open;
|
||||
// Tauri TabBar new-session: shell sessions + recent by last_used.
|
||||
let mut shell: Vec<_> = self
|
||||
.connections
|
||||
@@ -478,7 +480,7 @@ impl NyaTermApp {
|
||||
&mut self,
|
||||
cx: &mut Context<Self>,
|
||||
) -> impl IntoElement {
|
||||
let path = self.new_session_group_menu_path.clone();
|
||||
let path = self.shell.chrome.new_session_group_menu_path.clone();
|
||||
let visible_group_ids =
|
||||
new_session_visible_group_ids(&self.connections, &self.connection_groups);
|
||||
let mut parent_group_id = None;
|
||||
@@ -715,23 +717,26 @@ impl NyaTermApp {
|
||||
}
|
||||
|
||||
fn open_new_session_all_sessions_menu(&mut self, cx: &mut Context<Self>) {
|
||||
if !self.new_session_all_sessions_open {
|
||||
self.new_session_all_sessions_open = true;
|
||||
self.new_session_group_menu_path.clear();
|
||||
if !self.shell.chrome.new_session_all_sessions_open {
|
||||
self.shell.chrome.new_session_all_sessions_open = true;
|
||||
self.shell.chrome.new_session_group_menu_path.clear();
|
||||
cx.notify();
|
||||
}
|
||||
}
|
||||
|
||||
fn toggle_new_session_all_sessions_menu(&mut self, cx: &mut Context<Self>) {
|
||||
self.new_session_all_sessions_open = !self.new_session_all_sessions_open;
|
||||
self.new_session_group_menu_path.clear();
|
||||
self.shell.chrome.new_session_all_sessions_open =
|
||||
!self.shell.chrome.new_session_all_sessions_open;
|
||||
self.shell.chrome.new_session_group_menu_path.clear();
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
fn close_new_session_all_sessions_menu(&mut self, cx: &mut Context<Self>) {
|
||||
if self.new_session_all_sessions_open || !self.new_session_group_menu_path.is_empty() {
|
||||
self.new_session_all_sessions_open = false;
|
||||
self.new_session_group_menu_path.clear();
|
||||
if self.shell.chrome.new_session_all_sessions_open
|
||||
|| !self.shell.chrome.new_session_group_menu_path.is_empty()
|
||||
{
|
||||
self.shell.chrome.new_session_all_sessions_open = false;
|
||||
self.shell.chrome.new_session_group_menu_path.clear();
|
||||
cx.notify();
|
||||
}
|
||||
}
|
||||
@@ -742,21 +747,27 @@ impl NyaTermApp {
|
||||
depth: usize,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
let unchanged = self.new_session_all_sessions_open
|
||||
&& self.new_session_group_menu_path.get(depth) == Some(&group_id)
|
||||
&& self.new_session_group_menu_path.len() == depth + 1;
|
||||
let unchanged = self.shell.chrome.new_session_all_sessions_open
|
||||
&& self.shell.chrome.new_session_group_menu_path.get(depth) == Some(&group_id)
|
||||
&& self.shell.chrome.new_session_group_menu_path.len() == depth + 1;
|
||||
if unchanged {
|
||||
return;
|
||||
}
|
||||
self.new_session_all_sessions_open = true;
|
||||
self.new_session_group_menu_path.truncate(depth);
|
||||
self.new_session_group_menu_path.push(group_id);
|
||||
self.shell.chrome.new_session_all_sessions_open = true;
|
||||
self.shell
|
||||
.chrome
|
||||
.new_session_group_menu_path
|
||||
.truncate(depth);
|
||||
self.shell.chrome.new_session_group_menu_path.push(group_id);
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
fn truncate_new_session_group_menu(&mut self, depth: usize, cx: &mut Context<Self>) {
|
||||
if self.new_session_group_menu_path.len() > depth {
|
||||
self.new_session_group_menu_path.truncate(depth);
|
||||
if self.shell.chrome.new_session_group_menu_path.len() > depth {
|
||||
self.shell
|
||||
.chrome
|
||||
.new_session_group_menu_path
|
||||
.truncate(depth);
|
||||
cx.notify();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -368,7 +368,7 @@ impl NyaTermApp {
|
||||
.then_with(|| left.2.cmp(&right.2))
|
||||
.then_with(|| left.3.cmp(&right.3))
|
||||
});
|
||||
if self.session_tab_scroll_into_view_pending {
|
||||
if self.shell.chrome.session_tab_scroll_into_view_pending {
|
||||
if let Some(active_id) = self.session.active_id.as_deref() {
|
||||
if let Some(index) = sessions.iter().position(|session| session.id == active_id) {
|
||||
let pending_count = transient_tabs
|
||||
@@ -376,10 +376,13 @@ impl NyaTermApp {
|
||||
.filter(|(pending_index, _, _, _, _, _)| *pending_index <= index)
|
||||
.count();
|
||||
let child_index = index + pending_count;
|
||||
self.session_tab_strip_scroll.scroll_to_item(child_index);
|
||||
self.shell
|
||||
.chrome
|
||||
.session_tab_strip_scroll
|
||||
.scroll_to_item(child_index);
|
||||
}
|
||||
}
|
||||
self.session_tab_scroll_into_view_pending = false;
|
||||
self.shell.chrome.session_tab_scroll_into_view_pending = false;
|
||||
}
|
||||
let mut tabs = div()
|
||||
.id("session-tab-strip-scroll")
|
||||
@@ -391,7 +394,7 @@ impl NyaTermApp {
|
||||
// Tauri tab-strip-scroll: horizontal overflow instead of clipping tabs.
|
||||
.overflow_x_scroll()
|
||||
.overflow_y_hidden()
|
||||
.track_scroll(&self.session_tab_strip_scroll);
|
||||
.track_scroll(&self.shell.chrome.session_tab_strip_scroll);
|
||||
|
||||
let mut transient_cursor = 0usize;
|
||||
for (tab_index, session) in sessions.into_iter().enumerate() {
|
||||
@@ -436,7 +439,9 @@ impl NyaTermApp {
|
||||
.as_deref()
|
||||
.is_some_and(|id| self.tab_root_for_session(id) == session.id);
|
||||
let leaf_ids = self
|
||||
.session_pane_roots
|
||||
.shell
|
||||
.workspace
|
||||
.pane_roots
|
||||
.get(&session.id)
|
||||
.map(|root| root.session_ids())
|
||||
.unwrap_or_else(|| vec![session.id.clone()]);
|
||||
@@ -695,11 +700,17 @@ impl NyaTermApp {
|
||||
}
|
||||
|
||||
// Tauri TabBar trailing chrome: optional open-tabs overflow menu + new session menu.
|
||||
let open_tabs_menu = self.open_tabs_menu_open;
|
||||
let new_session_menu = self.new_session_menu_open;
|
||||
let open_tabs_menu = self.shell.chrome.open_tabs_menu_open;
|
||||
let new_session_menu = self.shell.chrome.new_session_menu_open;
|
||||
let open_tabs_label = self.tr("terminal.openTabs").to_string();
|
||||
let new_session_label = self.tr("terminal.newSession").to_string();
|
||||
let tab_strip_has_overflow = self.session_tab_strip_scroll.max_offset().width > px(0.);
|
||||
let tab_strip_has_overflow = self
|
||||
.shell
|
||||
.chrome
|
||||
.session_tab_strip_scroll
|
||||
.max_offset()
|
||||
.width
|
||||
> px(0.);
|
||||
// Tauri shows Open Tabs only when the strip actually overflows.
|
||||
let show_open_tabs_menu = tab_strip_has_overflow || open_tabs_menu;
|
||||
|
||||
|
||||
@@ -87,7 +87,7 @@ pub(in crate::features) use session::{
|
||||
pub(in crate::features) use settings::{SecurityFeatureFocus, SecurityFeatureState};
|
||||
pub(in crate::features) use shell::{
|
||||
ChromeTooltip, SessionTabDragPayload, SessionTabDragPreview, SessionTabTooltip,
|
||||
ShellFeatureState, TAB_MOUSE_ACTIONS, TabMouseActionTarget,
|
||||
ShellFeatureInit, ShellFeatureState, TAB_MOUSE_ACTIONS, TabMouseActionTarget,
|
||||
};
|
||||
pub(in crate::features) use shell::{
|
||||
appearance_font_options, appearance_font_stack, gpui_code_font_family,
|
||||
|
||||
@@ -59,7 +59,7 @@ impl NyaTermApp {
|
||||
cx: &mut Context<Self>,
|
||||
) -> AnyElement {
|
||||
let palette = self.theme_palette();
|
||||
let (viewport_width, viewport_height) = self.last_viewport_size;
|
||||
let (viewport_width, viewport_height) = self.shell.viewport.size;
|
||||
let max_height = connection_menu_max_height(viewport_height);
|
||||
let submenu_offset = connection_submenu_offset(f32::from(x), viewport_width);
|
||||
deferred(
|
||||
@@ -130,7 +130,7 @@ impl NyaTermApp {
|
||||
cx: &mut Context<Self>,
|
||||
) -> AnyElement {
|
||||
let palette = self.theme_palette();
|
||||
let max_height = connection_menu_max_height(self.last_viewport_size.1);
|
||||
let max_height = connection_menu_max_height(self.shell.viewport.size.1);
|
||||
let ungrouped_ids = move_ids.clone();
|
||||
let mut panel = div()
|
||||
.id(SharedString::from(id))
|
||||
|
||||
@@ -290,7 +290,7 @@ impl NyaTermApp {
|
||||
self.tr("dockerManager.compose").to_string(),
|
||||
],
|
||||
self.tr("common.more").to_string(),
|
||||
self.right_panel_width,
|
||||
self.shell.panels.right_width,
|
||||
self.remote_ops.docker.tab_menu_open,
|
||||
cx,
|
||||
))
|
||||
|
||||
@@ -99,7 +99,7 @@ impl NyaTermApp {
|
||||
.cloned()
|
||||
.collect::<Vec<_>>();
|
||||
// Responsive mode first so hidden columns do not keep invalid sort keys.
|
||||
let mode = process_display_mode(self.right_panel_width);
|
||||
let mode = process_display_mode(self.shell.panels.right_width);
|
||||
if mode != ProcessDisplayMode::Wide
|
||||
&& self.remote_ops.process.sort_key == RemoteProcessSortKey::User
|
||||
{
|
||||
|
||||
@@ -21,7 +21,7 @@ impl NyaTermApp {
|
||||
&mut self,
|
||||
cx: &mut Context<Self>,
|
||||
) -> impl IntoElement {
|
||||
self.settings_surface(self.last_viewport_size.0, false, cx)
|
||||
self.settings_surface(self.shell.viewport.size.0, false, cx)
|
||||
}
|
||||
|
||||
pub(in crate::features) fn settings_window_view(
|
||||
@@ -61,8 +61,8 @@ impl NyaTermApp {
|
||||
// Tauri SettingsPage shell: compact header + narrow nav + scroll content.
|
||||
let palette = self.theme_palette();
|
||||
let settings_title = self.tr("settings.title");
|
||||
let active_group = self.tr(self.settings_active_tab.group_i18n_key());
|
||||
let active_label = self.tr(self.settings_active_tab.i18n_key());
|
||||
let active_group = self.tr(self.shell.navigation.settings.active_tab.group_i18n_key());
|
||||
let active_label = self.tr(self.shell.navigation.settings.active_tab.i18n_key());
|
||||
let back_label = self.tr("common.close");
|
||||
div()
|
||||
.flex()
|
||||
@@ -303,9 +303,24 @@ impl NyaTermApp {
|
||||
} else {
|
||||
224.
|
||||
};
|
||||
let workspace_expanded = self.settings_expanded_groups.contains("workspace");
|
||||
let terminal_expanded = self.settings_expanded_groups.contains("terminal_session");
|
||||
let ai_expanded = self.settings_expanded_groups.contains("ai_group");
|
||||
let workspace_expanded = self
|
||||
.shell
|
||||
.navigation
|
||||
.settings
|
||||
.expanded_groups
|
||||
.contains("workspace");
|
||||
let terminal_expanded = self
|
||||
.shell
|
||||
.navigation
|
||||
.settings
|
||||
.expanded_groups
|
||||
.contains("terminal_session");
|
||||
let ai_expanded = self
|
||||
.shell
|
||||
.navigation
|
||||
.settings
|
||||
.expanded_groups
|
||||
.contains("ai_group");
|
||||
|
||||
let mut sidebar_nav = div()
|
||||
.id(SharedString::from("settings-sidebar-scroll"))
|
||||
@@ -540,8 +555,14 @@ impl NyaTermApp {
|
||||
this.tooltip(move |_, cx| cx.new(|_| ChromeTooltip::new(title)).into())
|
||||
})
|
||||
.on_click(cx.listener(move |this, _, _, cx| {
|
||||
if !this.settings_expanded_groups.insert(group.to_string()) {
|
||||
this.settings_expanded_groups.remove(group);
|
||||
if !this
|
||||
.shell
|
||||
.navigation
|
||||
.settings
|
||||
.expanded_groups
|
||||
.insert(group.to_string())
|
||||
{
|
||||
this.shell.navigation.settings.expanded_groups.remove(group);
|
||||
}
|
||||
cx.notify();
|
||||
}))
|
||||
@@ -556,7 +577,7 @@ impl NyaTermApp {
|
||||
cx: &mut Context<Self>,
|
||||
) -> impl IntoElement {
|
||||
let palette = self.theme_palette();
|
||||
let selected = self.settings_active_tab == tab;
|
||||
let selected = self.shell.navigation.settings.active_tab == tab;
|
||||
let label = self.tr(tab.i18n_key());
|
||||
|
||||
// Tauri settings nav item: soft primary fill, no permanent green border.
|
||||
@@ -619,7 +640,7 @@ impl NyaTermApp {
|
||||
this.tooltip(move |_, cx| cx.new(|_| ChromeTooltip::new(label)).into())
|
||||
})
|
||||
.on_click(cx.listener(move |this, _, _, cx| {
|
||||
this.settings_active_tab = tab;
|
||||
this.shell.navigation.settings.active_tab = tab;
|
||||
this.settings_state.appearance.menu_open = None;
|
||||
cx.notify();
|
||||
}))
|
||||
@@ -631,7 +652,7 @@ impl NyaTermApp {
|
||||
cx: &mut Context<Self>,
|
||||
) -> impl IntoElement {
|
||||
let palette = self.theme_palette();
|
||||
let active_tab = self.settings_active_tab;
|
||||
let active_tab = self.shell.navigation.settings.active_tab;
|
||||
let active_label = self.tr(active_tab.i18n_key());
|
||||
let content = self.settings_tab_content(active_tab, cx);
|
||||
let compact = viewport_width < 640.;
|
||||
|
||||
@@ -436,7 +436,8 @@ impl NyaTermApp {
|
||||
"cloud-open-security",
|
||||
self.tr("settings.openSecuritySettings"),
|
||||
cx.listener(|this, _, _, cx| {
|
||||
this.settings_active_tab = SettingsTab::Security;
|
||||
this.shell.navigation.settings.active_tab =
|
||||
SettingsTab::Security;
|
||||
cx.notify();
|
||||
}),
|
||||
)),
|
||||
|
||||
@@ -57,7 +57,7 @@ impl NyaTermApp {
|
||||
settings_switch(
|
||||
palette,
|
||||
"appearance-panel-multi-open",
|
||||
self.panel_multi_open,
|
||||
self.shell.panels.multi_open,
|
||||
cx.listener(|this, _, _, cx| {
|
||||
this.toggle_panel_multi_open(cx);
|
||||
}),
|
||||
|
||||
@@ -287,7 +287,7 @@ impl NyaTermApp {
|
||||
} else {
|
||||
// Tauri File Explorer virtual list (30px rows and overscan).
|
||||
let viewport_rows = transfer_browser_viewport_rows(
|
||||
self.last_viewport_size.1,
|
||||
self.shell.viewport.size.1,
|
||||
self.transfer.panel.height,
|
||||
self.transfer.browser.viewport_height,
|
||||
);
|
||||
@@ -527,7 +527,7 @@ impl NyaTermApp {
|
||||
let parent_count = usize::from(current_path != "/" && current_path != ".");
|
||||
let total = this.visible_transfer_browser_entries().len() + parent_count;
|
||||
let viewport_rows = transfer_browser_viewport_rows(
|
||||
this.last_viewport_size.1,
|
||||
this.shell.viewport.size.1,
|
||||
this.transfer.panel.height,
|
||||
this.transfer.browser.viewport_height,
|
||||
);
|
||||
|
||||
@@ -32,7 +32,7 @@ impl NyaTermApp {
|
||||
let description = self
|
||||
.tr("fileTransfer.deleteConfirmDesc")
|
||||
.replace("{{name}}", &state.title);
|
||||
let dialog_width = transfer_dialog_width(self.last_viewport_size.0, 320.);
|
||||
let dialog_width = transfer_dialog_width(self.shell.viewport.size.0, 320.);
|
||||
|
||||
div()
|
||||
.id(SharedString::from("transfer-job-delete-overlay"))
|
||||
@@ -152,7 +152,7 @@ impl NyaTermApp {
|
||||
let retry_id = state.job_id.clone();
|
||||
let open_id = state.job_id.clone();
|
||||
let delete_id = state.job_id.clone();
|
||||
let (viewport_w, viewport_h) = self.last_viewport_size;
|
||||
let (viewport_w, viewport_h) = self.shell.viewport.size;
|
||||
let (menu_x, menu_y, menu_max_height) = transfer_menu_position(
|
||||
f32::from(state.x),
|
||||
f32::from(state.y),
|
||||
|
||||
@@ -46,7 +46,7 @@ impl NyaTermApp {
|
||||
.map(|entry| self.enabled_transfer_file_ai_actions_for_entry(entry))
|
||||
.unwrap_or_default();
|
||||
let has_ai_actions = !selected_file_ai_actions.is_empty();
|
||||
let (viewport_w, viewport_h) = self.last_viewport_size;
|
||||
let (viewport_w, viewport_h) = self.shell.viewport.size;
|
||||
let preferred_height = if state.is_current_directory {
|
||||
380.
|
||||
} else if state.is_parent {
|
||||
|
||||
@@ -23,7 +23,7 @@ impl NyaTermApp {
|
||||
cx: &mut Context<Self>,
|
||||
) -> impl IntoElement {
|
||||
let palette = self.theme_palette();
|
||||
let dialog_width = transfer_dialog_width(self.last_viewport_size.0, 500.);
|
||||
let dialog_width = transfer_dialog_width(self.shell.viewport.size.0, 500.);
|
||||
let state = self
|
||||
.transfer
|
||||
.file_ops
|
||||
@@ -228,7 +228,7 @@ impl NyaTermApp {
|
||||
cx: &mut Context<Self>,
|
||||
) -> impl IntoElement {
|
||||
let palette = self.theme_palette();
|
||||
let dialog_width = transfer_dialog_width(self.last_viewport_size.0, 500.);
|
||||
let dialog_width = transfer_dialog_width(self.shell.viewport.size.0, 500.);
|
||||
let state = self
|
||||
.transfer
|
||||
.file_ops
|
||||
@@ -431,7 +431,7 @@ impl NyaTermApp {
|
||||
cx: &mut Context<Self>,
|
||||
) -> impl IntoElement {
|
||||
let palette = self.theme_palette();
|
||||
let dialog_width = transfer_dialog_width(self.last_viewport_size.0, 480.);
|
||||
let dialog_width = transfer_dialog_width(self.shell.viewport.size.0, 480.);
|
||||
let state = self
|
||||
.transfer
|
||||
.file_ops
|
||||
|
||||
@@ -42,7 +42,7 @@ impl NyaTermApp {
|
||||
.map(|path| remote_file_name(path))
|
||||
.collect::<Vec<_>>();
|
||||
let remaining_items = delete_count.saturating_sub(preview_items.len());
|
||||
let dialog_width = transfer_dialog_width(self.last_viewport_size.0, 320.);
|
||||
let dialog_width = transfer_dialog_width(self.shell.viewport.size.0, 320.);
|
||||
|
||||
div()
|
||||
.id(SharedString::from("transfer-delete-overlay"))
|
||||
@@ -170,7 +170,7 @@ impl NyaTermApp {
|
||||
cx,
|
||||
)
|
||||
.into_any_element();
|
||||
let dialog_width = transfer_dialog_width(self.last_viewport_size.0, 384.);
|
||||
let dialog_width = transfer_dialog_width(self.shell.viewport.size.0, 384.);
|
||||
|
||||
div()
|
||||
.id(SharedString::from("transfer-move-overlay"))
|
||||
|
||||
@@ -365,7 +365,7 @@ impl NyaTermApp {
|
||||
.min_w_0()
|
||||
.flex()
|
||||
.overflow_x_scroll();
|
||||
let tabs_menu_max_height = (self.last_viewport_size.1 - 48.).clamp(160., 360.);
|
||||
let tabs_menu_max_height = (self.shell.viewport.size.1 - 48.).clamp(160., 360.);
|
||||
let tabs_menu_bg = if standalone {
|
||||
rgb(palette.surface)
|
||||
} else {
|
||||
|
||||
@@ -57,7 +57,7 @@ impl NyaTermApp {
|
||||
.iter()
|
||||
.cloned()
|
||||
.collect::<Vec<_>>();
|
||||
let (viewport_w, viewport_h) = self.last_viewport_size;
|
||||
let (viewport_w, viewport_h) = self.shell.viewport.size;
|
||||
let (menu_x, menu_y, menu_max_height) = transfer_menu_position(
|
||||
f32::from(state.x),
|
||||
f32::from(state.y),
|
||||
|
||||
@@ -80,8 +80,8 @@ impl NyaTermApp {
|
||||
let property_mode = parse_transfer_mode(&state.mode_value)
|
||||
.or(entry.permissions)
|
||||
.unwrap_or(0o644);
|
||||
let dialog_width = (self.last_viewport_size.0 - 32.).min(460.).max(280.);
|
||||
let dialog_max_height = (self.last_viewport_size.1 * 0.75).clamp(320., 720.);
|
||||
let dialog_width = (self.shell.viewport.size.0 - 32.).min(460.).max(280.);
|
||||
let dialog_max_height = (self.shell.viewport.size.1 * 0.75).clamp(320., 720.);
|
||||
let owner_input = self
|
||||
.text_input_box(
|
||||
"transfer.properties.owner",
|
||||
|
||||
@@ -38,7 +38,7 @@ impl NyaTermApp {
|
||||
} else {
|
||||
state.entry.name.clone()
|
||||
};
|
||||
let dialog_width = transfer_dialog_width(self.last_viewport_size.0, 512.);
|
||||
let dialog_width = transfer_dialog_width(self.shell.viewport.size.0, 512.);
|
||||
let description = self
|
||||
.tr("fileExplorer.unknownFileTypeDesc")
|
||||
.replace("{{name}}", &name);
|
||||
|
||||
@@ -48,7 +48,7 @@ impl NyaTermApp {
|
||||
x: px(24.),
|
||||
y: px(24.),
|
||||
});
|
||||
let (viewport_w, viewport_h) = self.last_viewport_size;
|
||||
let (viewport_w, viewport_h) = self.shell.viewport.size;
|
||||
let (menu_x, menu_y, menu_max_height) = transfer_menu_position(
|
||||
f32::from(state.x),
|
||||
f32::from(state.y),
|
||||
|
||||
@@ -316,7 +316,7 @@ impl NyaTermApp {
|
||||
TransferBrowserChildrenMenuStatus::Error(_) => 112.,
|
||||
},
|
||||
};
|
||||
let (viewport_w, viewport_h) = self.last_viewport_size;
|
||||
let (viewport_w, viewport_h) = self.shell.viewport.size;
|
||||
let (menu_x, menu_y, menu_max_height) = transfer_menu_position(
|
||||
f32::from(menu.x),
|
||||
f32::from(menu.y),
|
||||
|
||||
@@ -17,8 +17,8 @@ impl NyaTermApp {
|
||||
// register/close/idle already keep terminal_windows coherent.
|
||||
let multi_leaf = self.terminal_windows_is_multi_leaf();
|
||||
let has_connect_failure = self.has_failed_session_start()
|
||||
|| (self.last_connect_failure_name.is_some()
|
||||
&& self.last_connect_failure_error.is_some());
|
||||
|| (self.shell.chrome.last_connect_failure_name.is_some()
|
||||
&& self.shell.chrome.last_connect_failure_error.is_some());
|
||||
let show_tab_strip = !multi_leaf
|
||||
&& (self.ordered_tab_session_count() > 0
|
||||
|| self.has_pending_session_start()
|
||||
@@ -52,8 +52,8 @@ impl NyaTermApp {
|
||||
return self.pending_workspace_state().into_any_element();
|
||||
}
|
||||
if self.has_failed_session_start()
|
||||
|| (self.last_connect_failure_name.is_some()
|
||||
&& self.last_connect_failure_error.is_some())
|
||||
|| (self.shell.chrome.last_connect_failure_name.is_some()
|
||||
&& self.shell.chrome.last_connect_failure_error.is_some())
|
||||
{
|
||||
return self.failed_workspace_state().into_any_element();
|
||||
}
|
||||
@@ -72,10 +72,10 @@ impl NyaTermApp {
|
||||
.into_any_element();
|
||||
}
|
||||
}
|
||||
let root = self
|
||||
.workspace_split
|
||||
.clone()
|
||||
.unwrap_or_else(|| WorkspacePaneNode::leaf(self.session.active_id.clone().unwrap()));
|
||||
let root =
|
||||
self.shell.workspace.split.clone().unwrap_or_else(|| {
|
||||
WorkspacePaneNode::leaf(self.session.active_id.clone().unwrap())
|
||||
});
|
||||
|
||||
let show_chrome = root.is_split();
|
||||
div()
|
||||
|
||||
@@ -77,7 +77,9 @@ impl NyaTermApp {
|
||||
.unwrap_or(("Session", "icons/conn/terminal.svg"));
|
||||
let custom_color = self.session.tab_colors.get(tab_id).copied();
|
||||
let leaf_ids = self
|
||||
.session_pane_roots
|
||||
.shell
|
||||
.workspace
|
||||
.pane_roots
|
||||
.get(tab_id)
|
||||
.map(|root| root.session_ids())
|
||||
.unwrap_or_else(|| vec![tab_id.clone()]);
|
||||
@@ -327,7 +329,8 @@ impl NyaTermApp {
|
||||
.on_click(cx.listener({
|
||||
let leaf_id = id.clone();
|
||||
move |this, _, window, cx| {
|
||||
this.focused_terminal_window_leaf_id = Some(leaf_id.clone());
|
||||
this.shell.workspace.focused_terminal_leaf_id =
|
||||
Some(leaf_id.clone());
|
||||
this.start_local_session(window, cx);
|
||||
}
|
||||
})),
|
||||
@@ -396,7 +399,8 @@ impl NyaTermApp {
|
||||
cx.listener({
|
||||
let leaf_id = id.clone();
|
||||
move |this, _, _, cx| {
|
||||
this.focused_terminal_window_leaf_id = Some(leaf_id.clone());
|
||||
this.shell.workspace.focused_terminal_leaf_id =
|
||||
Some(leaf_id.clone());
|
||||
cx.notify();
|
||||
}
|
||||
}),
|
||||
|
||||
@@ -29,7 +29,7 @@ impl NyaTermApp {
|
||||
} else {
|
||||
self.tr("tabCtx.disconnect").to_string()
|
||||
};
|
||||
let (viewport_w, viewport_h) = self.last_viewport_size;
|
||||
let (viewport_w, viewport_h) = self.shell.viewport.size;
|
||||
let (menu_x, menu_y) = active_session_clamped_menu_position(
|
||||
f32::from(menu.x) - 132.,
|
||||
f32::from(menu.y) + 8.,
|
||||
|
||||
@@ -13,8 +13,8 @@ impl NyaTermApp {
|
||||
cx: &mut Context<Self>,
|
||||
) -> impl IntoElement {
|
||||
let palette = self.theme_palette();
|
||||
let narrow = self.last_viewport_size.0 < 520.;
|
||||
let width = (self.last_viewport_size.0 - 32.).clamp(280., 480.);
|
||||
let narrow = self.shell.viewport.size.0 < 520.;
|
||||
let width = (self.shell.viewport.size.0 - 32.).clamp(280., 480.);
|
||||
let docs_url = if self
|
||||
.settings
|
||||
.language
|
||||
|
||||
@@ -14,7 +14,7 @@ impl NyaTermApp {
|
||||
cx: &mut Context<Self>,
|
||||
) -> impl IntoElement {
|
||||
let palette = self.theme_palette();
|
||||
let (viewport_w, viewport_h) = self.last_viewport_size;
|
||||
let (viewport_w, viewport_h) = self.shell.viewport.size;
|
||||
let draft = self
|
||||
.terminal
|
||||
.paste
|
||||
|
||||
@@ -14,7 +14,7 @@ impl NyaTermApp {
|
||||
let category_id = menu.category_id.clone();
|
||||
let rename_id = category_id.clone();
|
||||
let delete_id = category_id.clone();
|
||||
let (viewport_w, viewport_h) = self.last_viewport_size;
|
||||
let (viewport_w, viewport_h) = self.shell.viewport.size;
|
||||
let (menu_x, menu_y) = quick_command_category_clamped_menu_position(
|
||||
f32::from(menu.x),
|
||||
f32::from(menu.y),
|
||||
|
||||
@@ -38,7 +38,7 @@ impl NyaTermApp {
|
||||
.child(
|
||||
div()
|
||||
.id(SharedString::from("quick-command-category-delete-dialog"))
|
||||
.w(px((self.last_viewport_size.0 - 32.).clamp(280., 384.)))
|
||||
.w(px((self.shell.viewport.size.0 - 32.).clamp(280., 384.)))
|
||||
.rounded_md()
|
||||
.border_1()
|
||||
.border_color(rgb(0x7f1d1d))
|
||||
@@ -136,7 +136,7 @@ impl NyaTermApp {
|
||||
.child(
|
||||
div()
|
||||
.id(SharedString::from("quick-command-category-rename-dialog"))
|
||||
.w(px((self.last_viewport_size.0 - 32.).clamp(280., 384.)))
|
||||
.w(px((self.shell.viewport.size.0 - 32.).clamp(280., 384.)))
|
||||
.rounded_md()
|
||||
.border_1()
|
||||
.border_color(rgb(palette.border))
|
||||
|
||||
@@ -35,7 +35,7 @@ impl NyaTermApp {
|
||||
.child(
|
||||
div()
|
||||
.id(SharedString::from("quick-command-delete-dialog"))
|
||||
.w(px((self.last_viewport_size.0 - 32.).clamp(280., 384.)))
|
||||
.w(px((self.shell.viewport.size.0 - 32.).clamp(280., 384.)))
|
||||
.rounded_md()
|
||||
.border_1()
|
||||
.border_color(rgb(0x7f1d1d))
|
||||
|
||||
@@ -30,7 +30,7 @@ impl NyaTermApp {
|
||||
.filter(|description| !description.is_empty());
|
||||
let command_text = command.command.clone();
|
||||
let estimated_h = if description.is_some() { 224. } else { 182. };
|
||||
let (viewport_w, viewport_h) = self.last_viewport_size;
|
||||
let (viewport_w, viewport_h) = self.shell.viewport.size;
|
||||
let (popover_x, popover_y) = quick_command_details_popover_position(
|
||||
f32::from(anchor_x) - 304.,
|
||||
f32::from(anchor_y) - estimated_h - 6.,
|
||||
|
||||
@@ -17,7 +17,7 @@ impl NyaTermApp {
|
||||
&mut self,
|
||||
cx: &mut Context<Self>,
|
||||
) -> AnyElement {
|
||||
self.quick_command_editor_surface(self.last_viewport_size.0, false, cx)
|
||||
self.quick_command_editor_surface(self.shell.viewport.size.0, false, cx)
|
||||
}
|
||||
|
||||
pub(in crate::features) fn quick_command_editor_window_view(
|
||||
|
||||
@@ -48,7 +48,7 @@ impl NyaTermApp {
|
||||
.child(
|
||||
div()
|
||||
.id(SharedString::from("quick-command-import-dialog"))
|
||||
.w(px((self.last_viewport_size.0 - 32.).clamp(280., 380.)))
|
||||
.w(px((self.shell.viewport.size.0 - 32.).clamp(280., 380.)))
|
||||
.max_w_full()
|
||||
.rounded_md()
|
||||
.border_1()
|
||||
|
||||
@@ -16,7 +16,7 @@ impl NyaTermApp {
|
||||
let all_command_id = command_id.clone();
|
||||
let delete_command_id = command_id.clone();
|
||||
let can_send_to_all = self.live_session_count() > 1;
|
||||
let (viewport_w, viewport_h) = self.last_viewport_size;
|
||||
let (viewport_w, viewport_h) = self.shell.viewport.size;
|
||||
let (menu_x, menu_y) = quick_command_clamped_menu_position(
|
||||
f32::from(menu.x),
|
||||
f32::from(menu.y),
|
||||
|
||||
@@ -147,7 +147,7 @@ impl NyaTermApp {
|
||||
.child(
|
||||
div()
|
||||
.id(SharedString::from("quick-command-variable-dialog"))
|
||||
.w(px((self.last_viewport_size.0 - 32.).clamp(280., 400.)))
|
||||
.w(px((self.shell.viewport.size.0 - 32.).clamp(280., 400.)))
|
||||
.max_w_full()
|
||||
.rounded_md()
|
||||
.border_1()
|
||||
@@ -173,7 +173,7 @@ impl NyaTermApp {
|
||||
div()
|
||||
.id("quick-command-variable-body")
|
||||
.p_5()
|
||||
.max_h(px((self.last_viewport_size.1 * 0.6).clamp(180., 420.)))
|
||||
.max_h(px((self.shell.viewport.size.1 * 0.6).clamp(180., 420.)))
|
||||
.overflow_y_scroll()
|
||||
.child(rows)
|
||||
.child(
|
||||
|
||||
@@ -63,11 +63,11 @@ impl NyaTermApp {
|
||||
let category_sidebar = self.quick_command_category_sidebar(categories, palette, cx);
|
||||
let view_mode = self.quick_command_state.list.view_mode;
|
||||
let tile_columns = quick_command_tile_column_count(
|
||||
self.last_viewport_size.0,
|
||||
self.left_panel_width,
|
||||
self.right_panel_width,
|
||||
!self.left_sidebar_collapsed,
|
||||
!self.right_inspector_collapsed,
|
||||
self.shell.viewport.size.0,
|
||||
self.shell.panels.left_width,
|
||||
self.shell.panels.right_width,
|
||||
!self.shell.panels.left_collapsed,
|
||||
!self.shell.panels.right_collapsed,
|
||||
);
|
||||
let logical_row_height = match view_mode {
|
||||
QuickCommandViewMode::Tile => 32.,
|
||||
|
||||
@@ -14,7 +14,7 @@ impl NyaTermApp {
|
||||
cx: &mut Context<Self>,
|
||||
) -> impl IntoElement {
|
||||
let palette = self.theme_palette();
|
||||
let (viewport_w, viewport_h) = self.last_viewport_size;
|
||||
let (viewport_w, viewport_h) = self.shell.viewport.size;
|
||||
let items = self.filtered_quick_switch_items(cx);
|
||||
self.update_quick_switch_state(cx, |store| {
|
||||
store.clamp_quick_switch_selected_index(items.len())
|
||||
@@ -28,7 +28,7 @@ impl NyaTermApp {
|
||||
cx,
|
||||
);
|
||||
let query_focus = query_input.read(cx).focus_handle();
|
||||
let list_max_height = (self.last_viewport_size.1 * 0.55).clamp(160., 384.);
|
||||
let list_max_height = (self.shell.viewport.size.1 * 0.55).clamp(160., 384.);
|
||||
let selected_row_bg = rgba((palette.primary << 8) | 0x26);
|
||||
let hover_row_bg = self.shell_surface_color(palette.hover);
|
||||
let mut rows = div()
|
||||
|
||||
@@ -54,7 +54,7 @@ impl NyaTermApp {
|
||||
.child(
|
||||
div()
|
||||
.id("close-all-sessions-confirm-dialog")
|
||||
.w(px((self.last_viewport_size.0 - 32.).clamp(280., 400.)))
|
||||
.w(px((self.shell.viewport.size.0 - 32.).clamp(280., 400.)))
|
||||
.max_w_full()
|
||||
.mx_4()
|
||||
.rounded_md()
|
||||
|
||||
@@ -23,7 +23,7 @@ impl NyaTermApp {
|
||||
)
|
||||
.into_any_element();
|
||||
let can_save = !self.session.dialogs.rename_draft.trim().is_empty();
|
||||
let dialog_width = (self.last_viewport_size.0 - 32.).clamp(280., 320.);
|
||||
let dialog_width = (self.shell.viewport.size.0 - 32.).clamp(280., 320.);
|
||||
|
||||
div()
|
||||
.id(SharedString::from("rename-tab-overlay"))
|
||||
@@ -350,7 +350,7 @@ impl NyaTermApp {
|
||||
.into_any_element();
|
||||
let can_submit = !self.session.dialogs.startup_command_draft.trim().is_empty();
|
||||
let delay_label = format!("{} ms", self.session.dialogs.startup_command_delay_ms);
|
||||
let dialog_width = (self.last_viewport_size.0 - 32.).clamp(280., 448.);
|
||||
let dialog_width = (self.shell.viewport.size.0 - 32.).clamp(280., 448.);
|
||||
|
||||
div()
|
||||
.id(SharedString::from("startup-command-overlay"))
|
||||
|
||||
@@ -17,7 +17,7 @@ impl NyaTermApp {
|
||||
cx: &mut Context<Self>,
|
||||
) -> impl IntoElement {
|
||||
let palette = self.theme_palette();
|
||||
let (viewport_w, viewport_h) = self.last_viewport_size;
|
||||
let (viewport_w, viewport_h) = self.shell.viewport.size;
|
||||
let dialog_width = (viewport_w - 24.).clamp(280., 900.);
|
||||
let dialog_height = (viewport_h - 56.).clamp(280., 500.);
|
||||
let groups_width = (dialog_width * 0.23).clamp(160., 208.);
|
||||
|
||||
@@ -33,7 +33,7 @@ impl NyaTermApp {
|
||||
_session_count: usize,
|
||||
cx: &mut Context<Self>,
|
||||
) -> gpui::AnyElement {
|
||||
let (viewport_w, viewport_h) = self.last_viewport_size;
|
||||
let (viewport_w, viewport_h) = self.shell.viewport.size;
|
||||
let menu_max_height = (viewport_h - 16.).clamp(160., 440.);
|
||||
let (menu_x, menu_y) = if let Some((x, y)) = self.session.dialogs.tab_actions_anchor {
|
||||
clamp_tab_actions_position(x, y, 240., menu_max_height, viewport_w, viewport_h)
|
||||
|
||||
@@ -69,9 +69,9 @@ impl NyaTermApp {
|
||||
.active_id
|
||||
.as_deref()
|
||||
.map(|id| self.tab_root_for_session(id))
|
||||
.and_then(|root| self.session_pane_roots.get(&root))
|
||||
.and_then(|root| self.shell.workspace.pane_roots.get(&root))
|
||||
.is_some_and(|root| root.is_split())
|
||||
|| self.workspace_split.is_some();
|
||||
|| self.shell.workspace.split.is_some();
|
||||
let scroll_offset = self
|
||||
.terminal
|
||||
.view
|
||||
|
||||
@@ -55,7 +55,7 @@ impl NyaTermApp {
|
||||
.child(
|
||||
div()
|
||||
.id(SharedString::from("temporary-ssh-link-dialog"))
|
||||
.w(px((self.last_viewport_size.0 - 32.).clamp(280., 480.)))
|
||||
.w(px((self.shell.viewport.size.0 - 32.).clamp(280., 480.)))
|
||||
.max_w_full()
|
||||
.mx_4()
|
||||
.rounded_md()
|
||||
|
||||
@@ -14,7 +14,7 @@ impl NyaTermApp {
|
||||
cx: &mut Context<Self>,
|
||||
) -> impl IntoElement {
|
||||
let palette = self.theme_palette();
|
||||
let (viewport_w, viewport_h) = self.last_viewport_size;
|
||||
let (viewport_w, viewport_h) = self.shell.viewport.size;
|
||||
let visible_text = self.active_terminal_visible_text();
|
||||
let buffer_tail = self.active_terminal_buffer_tail();
|
||||
let visible_lines = visible_text.lines().count();
|
||||
@@ -347,7 +347,7 @@ impl NyaTermApp {
|
||||
self.tr("terminalActions.sessionLog"),
|
||||
cx.listener(|this, _, _, cx| {
|
||||
this.terminal.menus.actions_open = false;
|
||||
this.right_focus = RightFocus::Recording;
|
||||
this.shell.panels.right_focus = RightFocus::Recording;
|
||||
cx.notify();
|
||||
}),
|
||||
))
|
||||
|
||||
@@ -21,8 +21,8 @@ impl NyaTermApp {
|
||||
let available =
|
||||
!checking && !failed && update_info.as_ref().is_some_and(|info| info.available);
|
||||
let portable = self.runtime.mode() == RuntimeMode::Portable;
|
||||
let (_, viewport_h) = self.last_viewport_size;
|
||||
let dialog_width = (self.last_viewport_size.0 - 32.).clamp(320., 560.);
|
||||
let (_, viewport_h) = self.shell.viewport.size;
|
||||
let dialog_width = (self.shell.viewport.size.0 - 32.).clamp(320., 560.);
|
||||
let release_url = update_info
|
||||
.as_ref()
|
||||
.and_then(|info| info.html_url.clone())
|
||||
|
||||
@@ -233,7 +233,7 @@ impl NyaTermApp {
|
||||
if !command.ends_with('\n') {
|
||||
command.push('\n');
|
||||
}
|
||||
self.selected_nav = NavItem::Workspace;
|
||||
self.shell.navigation.selected_nav = NavItem::Workspace;
|
||||
if self.send_terminal_input(command.into_bytes(), cx) {
|
||||
self.remote_ops.docker.status = status;
|
||||
self.terminal.view.status = self.remote_ops.docker.status.clone();
|
||||
|
||||
@@ -82,23 +82,23 @@ impl NyaTermApp {
|
||||
.on_mouse_down(
|
||||
MouseButton::Left,
|
||||
cx.listener(|this, _, _, cx| {
|
||||
let changed = this.title_menu_open.is_some()
|
||||
|| this.header_status.menu_open
|
||||
|| this.open_tabs_menu_open
|
||||
|| this.new_session_menu_open
|
||||
|| this.new_session_all_sessions_open
|
||||
|| !this.new_session_group_menu_path.is_empty()
|
||||
let changed = this.shell.chrome.title_menu_open.is_some()
|
||||
|| this.shell.chrome.header_status.menu_open
|
||||
|| this.shell.chrome.open_tabs_menu_open
|
||||
|| this.shell.chrome.new_session_menu_open
|
||||
|| this.shell.chrome.new_session_all_sessions_open
|
||||
|| !this.shell.chrome.new_session_group_menu_path.is_empty()
|
||||
|| this.remote_ops.docker.tab_menu_open
|
||||
|| this.remote_ops.docker.header_menu_open
|
||||
|| this.connection_state.list_more_menu_is_open();
|
||||
if changed {
|
||||
this.title_menu_open = None;
|
||||
this.title_menu_submenu = None;
|
||||
this.header_status.menu_open = false;
|
||||
this.open_tabs_menu_open = false;
|
||||
this.new_session_menu_open = false;
|
||||
this.new_session_all_sessions_open = false;
|
||||
this.new_session_group_menu_path.clear();
|
||||
this.shell.chrome.title_menu_open = None;
|
||||
this.shell.chrome.title_menu_submenu = None;
|
||||
this.shell.chrome.header_status.menu_open = false;
|
||||
this.shell.chrome.open_tabs_menu_open = false;
|
||||
this.shell.chrome.new_session_menu_open = false;
|
||||
this.shell.chrome.new_session_all_sessions_open = false;
|
||||
this.shell.chrome.new_session_group_menu_path.clear();
|
||||
this.remote_ops.docker.tab_menu_open = false;
|
||||
this.remote_ops.docker.header_menu_open = false;
|
||||
this.connection_state.close_list_more_menu();
|
||||
@@ -205,7 +205,7 @@ impl NyaTermApp {
|
||||
.into();
|
||||
if let Some((tile_width, tile_height)) = wallpaper_tile_size {
|
||||
let (columns, rows) =
|
||||
wallpaper_tile_grid(self.last_viewport_size, (tile_width, tile_height));
|
||||
wallpaper_tile_grid(self.shell.viewport.size, (tile_width, tile_height));
|
||||
let mut layer = div()
|
||||
.absolute()
|
||||
.inset_0()
|
||||
@@ -251,11 +251,12 @@ impl NyaTermApp {
|
||||
}
|
||||
|
||||
fn wallpaper_tile_size(&mut self, path: &str) -> (f32, f32) {
|
||||
if let Some((cached_path, width, height)) = self.wallpaper_tile_dimensions.as_ref()
|
||||
if let Some((cached_path, width, height)) =
|
||||
self.shell.viewport.wallpaper_tile_dimensions.as_ref()
|
||||
&& cached_path == path
|
||||
{
|
||||
return fit_wallpaper_tile_size(
|
||||
self.last_viewport_size,
|
||||
self.shell.viewport.size,
|
||||
(*width as f32, *height as f32),
|
||||
);
|
||||
}
|
||||
@@ -263,8 +264,8 @@ impl NyaTermApp {
|
||||
.ok()
|
||||
.filter(|(width, height)| *width > 0 && *height > 0)
|
||||
.unwrap_or((256, 256));
|
||||
self.wallpaper_tile_dimensions = Some((path.to_string(), width, height));
|
||||
fit_wallpaper_tile_size(self.last_viewport_size, (width as f32, height as f32))
|
||||
self.shell.viewport.wallpaper_tile_dimensions = Some((path.to_string(), width, height));
|
||||
fit_wallpaper_tile_size(self.shell.viewport.size, (width as f32, height as f32))
|
||||
}
|
||||
|
||||
fn workspace_surface(
|
||||
@@ -273,7 +274,9 @@ impl NyaTermApp {
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) -> AnyElement {
|
||||
if self.main_mode == MainMode::Page && self.selected_nav == NavItem::Settings {
|
||||
if self.shell.navigation.main_mode == MainMode::Page
|
||||
&& self.shell.navigation.selected_nav == NavItem::Settings
|
||||
{
|
||||
div()
|
||||
.flex()
|
||||
.flex_1()
|
||||
@@ -285,15 +288,15 @@ impl NyaTermApp {
|
||||
let compact_layout = !cfg!(target_os = "macos");
|
||||
let has_left_activity_items = self.activity_side_has_items(ActivitySide::Left);
|
||||
let has_right_activity_items = self.activity_side_has_items(ActivitySide::Right);
|
||||
let left_overlay_mode = compact_layout && self.last_viewport_size.0 < 1024.;
|
||||
let right_overlay_mode = compact_layout && self.last_viewport_size.0 < 768.;
|
||||
let left_overlay_mode = compact_layout && self.shell.viewport.size.0 < 1024.;
|
||||
let right_overlay_mode = compact_layout && self.shell.viewport.size.0 < 768.;
|
||||
let left_drawer_open = has_left_activity_items
|
||||
&& left_overlay_mode
|
||||
&& self.mobile_left_open
|
||||
&& self.shell.panels.mobile_left_open
|
||||
&& self.left_side_open();
|
||||
let right_drawer_open = has_right_activity_items
|
||||
&& right_overlay_mode
|
||||
&& self.mobile_right_open
|
||||
&& self.shell.panels.mobile_right_open
|
||||
&& self.right_side_open();
|
||||
let mut surface = div()
|
||||
.flex()
|
||||
@@ -324,7 +327,7 @@ impl NyaTermApp {
|
||||
this.child(self.activity_bar(ActivitySide::Right, cx))
|
||||
});
|
||||
|
||||
if self.terminal_windows_is_multi_leaf() && self.new_session_menu_open {
|
||||
if self.terminal_windows_is_multi_leaf() && self.shell.chrome.new_session_menu_open {
|
||||
surface = surface.child(self.render_new_session_menu(cx));
|
||||
}
|
||||
|
||||
@@ -336,8 +339,8 @@ impl NyaTermApp {
|
||||
.inset_0()
|
||||
.bg(rgba(0x00000080))
|
||||
.on_click(cx.listener(|this, _, _, cx| {
|
||||
this.mobile_left_open = false;
|
||||
this.mobile_right_open = false;
|
||||
this.shell.panels.mobile_left_open = false;
|
||||
this.shell.panels.mobile_right_open = false;
|
||||
cx.notify();
|
||||
})),
|
||||
);
|
||||
@@ -439,9 +442,9 @@ impl NyaTermApp {
|
||||
)
|
||||
.on_click(cx.listener(move |this, _, _, cx| {
|
||||
if left {
|
||||
this.mobile_left_open = false;
|
||||
this.shell.panels.mobile_left_open = false;
|
||||
} else {
|
||||
this.mobile_right_open = false;
|
||||
this.shell.panels.mobile_right_open = false;
|
||||
}
|
||||
cx.notify();
|
||||
})),
|
||||
@@ -633,9 +636,10 @@ impl NyaTermApp {
|
||||
.when(quick_switch_open, |this| {
|
||||
this.child(self.quick_switch_overlay(cx))
|
||||
})
|
||||
.when(self.activity_bar_context_menu.is_some(), |this| {
|
||||
this.child(self.activity_bar_context_menu_overlay(cx))
|
||||
})
|
||||
.when(
|
||||
self.shell.chrome.activity_bar_context_menu.is_some(),
|
||||
|this| this.child(self.activity_bar_context_menu_overlay(cx)),
|
||||
)
|
||||
.when(overlay.locked, |this| {
|
||||
this.child(self.lock_screen_overlay(window, cx))
|
||||
})
|
||||
@@ -657,8 +661,8 @@ impl NyaTermApp {
|
||||
}
|
||||
|
||||
fn modal_child_window_open(&self) -> bool {
|
||||
self.settings_window.is_some()
|
||||
|| self.settings_window_open_pending
|
||||
self.shell.navigation.settings.window.is_some()
|
||||
|| self.shell.navigation.settings.window_open_pending
|
||||
|| self.quick_command_state.editor.window.is_some()
|
||||
|| self.quick_command_state.editor.window_open_pending
|
||||
|| self.connection_state.editor_modal_window_open_or_pending()
|
||||
@@ -667,9 +671,9 @@ impl NyaTermApp {
|
||||
}
|
||||
|
||||
fn activate_modal_child_window(&mut self, cx: &mut Context<Self>) -> bool {
|
||||
if self.settings_window.is_some() {
|
||||
if self.shell.navigation.settings.window.is_some() {
|
||||
self.activate_settings_window(cx)
|
||||
} else if self.settings_window_open_pending {
|
||||
} else if self.shell.navigation.settings.window_open_pending {
|
||||
true
|
||||
} else if self.quick_command_state.editor.window.is_some() {
|
||||
self.activate_quick_command_window(cx)
|
||||
|
||||
@@ -214,9 +214,8 @@ impl NyaTermApp {
|
||||
self.clear_terminal_mouse_report_for_session(session_id);
|
||||
self.session.start.reconnect_failures.remove(session_id);
|
||||
self.session.order.retain(|id| id != session_id);
|
||||
self.session_tab_owner.remove(session_id);
|
||||
// If this leaf was a tab root, drop its pane tree (prune will rekey survivors).
|
||||
self.session_pane_roots.remove(session_id);
|
||||
self.shell.workspace.remove_session(session_id);
|
||||
let multiplex_key = self
|
||||
.session
|
||||
.metadata
|
||||
|
||||
@@ -105,8 +105,8 @@ impl NyaTermApp {
|
||||
);
|
||||
}
|
||||
}
|
||||
self.selected_nav = NavItem::Workspace;
|
||||
self.main_mode = MainMode::Workspace;
|
||||
self.shell.navigation.selected_nav = NavItem::Workspace;
|
||||
self.shell.navigation.main_mode = MainMode::Workspace;
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
@@ -169,8 +169,8 @@ impl NyaTermApp {
|
||||
existing_multiplex,
|
||||
cx,
|
||||
);
|
||||
self.selected_nav = NavItem::Workspace;
|
||||
self.main_mode = MainMode::Workspace;
|
||||
self.shell.navigation.selected_nav = NavItem::Workspace;
|
||||
self.shell.navigation.main_mode = MainMode::Workspace;
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
@@ -420,8 +420,8 @@ impl NyaTermApp {
|
||||
self.session
|
||||
.busy_actions
|
||||
.retain(|id, _| self.session.metadata.contains_key(id));
|
||||
self.selected_nav = NavItem::Workspace;
|
||||
self.main_mode = MainMode::Workspace;
|
||||
self.shell.navigation.selected_nav = NavItem::Workspace;
|
||||
self.shell.navigation.main_mode = MainMode::Workspace;
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
@@ -463,17 +463,7 @@ impl NyaTermApp {
|
||||
.insert(new_id.to_string(), history);
|
||||
}
|
||||
|
||||
let mut pane_roots = std::mem::take(&mut self.session_pane_roots);
|
||||
for root in pane_roots.values_mut() {
|
||||
root.replace_session_id(old_id, new_id);
|
||||
}
|
||||
if let Some(root) = pane_roots.remove(old_id) {
|
||||
pane_roots.insert(new_id.to_string(), root);
|
||||
}
|
||||
self.session_pane_roots = pane_roots;
|
||||
if let Some(root) = self.workspace_split.as_mut() {
|
||||
root.replace_session_id(old_id, new_id);
|
||||
}
|
||||
self.shell.workspace.replace_session_id(old_id, new_id);
|
||||
if let Some(root) = self.terminal.windows.tree.as_mut() {
|
||||
root.replace_tab_id(old_id, new_id);
|
||||
}
|
||||
@@ -481,7 +471,6 @@ impl NyaTermApp {
|
||||
if self.session.active_id.as_deref() == Some(old_id) {
|
||||
self.activate_session_id(new_id);
|
||||
}
|
||||
self.rebuild_session_tab_owners();
|
||||
self.sync_workspace_split_from_active_tab();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -128,7 +128,9 @@ impl NyaTermApp {
|
||||
/// True when this session is a secondary leaf inside another tab's pane tree
|
||||
/// (Tauri: multiple SessionPanes under one Tab, only one strip entry).
|
||||
pub(in crate::features) fn is_secondary_pane_session(&self, session_id: &str) -> bool {
|
||||
self.session_tab_owner
|
||||
self.shell
|
||||
.workspace
|
||||
.tab_owner
|
||||
.get(session_id)
|
||||
.is_some_and(|owner| owner != session_id)
|
||||
}
|
||||
@@ -138,7 +140,7 @@ impl NyaTermApp {
|
||||
let mut current = session_id.to_string();
|
||||
// Flatten owner chains defensively.
|
||||
for _ in 0..8 {
|
||||
match self.session_tab_owner.get(¤t) {
|
||||
match self.shell.workspace.tab_owner.get(¤t) {
|
||||
Some(owner) if owner != ¤t => current = owner.clone(),
|
||||
_ => break,
|
||||
}
|
||||
@@ -176,7 +178,7 @@ impl NyaTermApp {
|
||||
return active.to_string();
|
||||
}
|
||||
}
|
||||
if let Some(root) = self.session_pane_roots.get(tab_root) {
|
||||
if let Some(root) = self.shell.workspace.pane_roots.get(tab_root) {
|
||||
if let Some(first) = root.session_ids().into_iter().next() {
|
||||
return first;
|
||||
}
|
||||
|
||||
@@ -50,10 +50,10 @@ impl NyaTermApp {
|
||||
if !self.session.start.select_pending(&request_id) {
|
||||
return;
|
||||
}
|
||||
self.open_tabs_menu_open = false;
|
||||
self.new_session_menu_open = false;
|
||||
self.selected_nav = NavItem::Workspace;
|
||||
self.main_mode = MainMode::Workspace;
|
||||
self.shell.chrome.open_tabs_menu_open = false;
|
||||
self.shell.chrome.new_session_menu_open = false;
|
||||
self.shell.navigation.selected_nav = NavItem::Workspace;
|
||||
self.shell.navigation.main_mode = MainMode::Workspace;
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
@@ -80,10 +80,10 @@ impl NyaTermApp {
|
||||
if !self.session.start.select_failed(&request_id) {
|
||||
return;
|
||||
}
|
||||
self.open_tabs_menu_open = false;
|
||||
self.new_session_menu_open = false;
|
||||
self.selected_nav = NavItem::Workspace;
|
||||
self.main_mode = MainMode::Workspace;
|
||||
self.shell.chrome.open_tabs_menu_open = false;
|
||||
self.shell.chrome.new_session_menu_open = false;
|
||||
self.shell.navigation.selected_nav = NavItem::Workspace;
|
||||
self.shell.navigation.main_mode = MainMode::Workspace;
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
@@ -96,8 +96,8 @@ impl NyaTermApp {
|
||||
return;
|
||||
};
|
||||
if !self.session.start.has_failed() {
|
||||
self.last_connect_failure_name = None;
|
||||
self.last_connect_failure_error = None;
|
||||
self.shell.chrome.last_connect_failure_name = None;
|
||||
self.shell.chrome.last_connect_failure_error = None;
|
||||
}
|
||||
self.terminal.view.status = format!(
|
||||
"closed failed connection {}",
|
||||
@@ -136,8 +136,8 @@ impl NyaTermApp {
|
||||
} = registration;
|
||||
|
||||
if reconnect_session_id.is_none() {
|
||||
self.last_connect_failure_name = None;
|
||||
self.last_connect_failure_error = None;
|
||||
self.shell.chrome.last_connect_failure_name = None;
|
||||
self.shell.chrome.last_connect_failure_error = None;
|
||||
}
|
||||
self.session.start.panes.insert(
|
||||
request_id.clone(),
|
||||
@@ -174,8 +174,8 @@ impl NyaTermApp {
|
||||
// Status + connecting tab already show progress; avoid full terminal decode
|
||||
// work on the click path before the worker even starts.
|
||||
let _ = append_start_log;
|
||||
self.selected_nav = NavItem::Workspace;
|
||||
self.main_mode = MainMode::Workspace;
|
||||
self.shell.navigation.selected_nav = NavItem::Workspace;
|
||||
self.shell.navigation.main_mode = MainMode::Workspace;
|
||||
cx.notify();
|
||||
request_id
|
||||
}
|
||||
@@ -469,8 +469,8 @@ impl NyaTermApp {
|
||||
match event.result {
|
||||
Ok(success) => {
|
||||
let ui_register_started_at = Instant::now();
|
||||
self.last_connect_failure_name = None;
|
||||
self.last_connect_failure_error = None;
|
||||
self.shell.chrome.last_connect_failure_name = None;
|
||||
self.shell.chrome.last_connect_failure_error = None;
|
||||
let session_info = success.session_info;
|
||||
let session_id = session_info.id.clone();
|
||||
let reconnect_session_id = pending
|
||||
@@ -640,7 +640,7 @@ impl NyaTermApp {
|
||||
self.schedule_startup_command(session_id.clone(), startup_command, cx);
|
||||
}
|
||||
self.apply_pending_workspace_split_for_duplicate(&session_id);
|
||||
self.selected_nav = NavItem::Workspace;
|
||||
self.shell.navigation.selected_nav = NavItem::Workspace;
|
||||
let ui_register_duration = ui_register_started_at.elapsed();
|
||||
let request_to_ui_duration = requested_at
|
||||
.map(|requested_at| requested_at.elapsed())
|
||||
@@ -685,8 +685,8 @@ impl NyaTermApp {
|
||||
let _ = self.session.start.reconnect_replace_id.take();
|
||||
}
|
||||
if !reconnect_failure {
|
||||
self.last_connect_failure_name = Some(connection_name.clone());
|
||||
self.last_connect_failure_error = Some(error.clone());
|
||||
self.shell.chrome.last_connect_failure_name = Some(connection_name.clone());
|
||||
self.shell.chrome.last_connect_failure_error = Some(error.clone());
|
||||
}
|
||||
if let Some(session_id) = reconnect_session_id {
|
||||
if self.session.metadata.contains_key(&session_id) {
|
||||
@@ -725,7 +725,7 @@ impl NyaTermApp {
|
||||
connection_name
|
||||
));
|
||||
}
|
||||
self.selected_nav = NavItem::Workspace;
|
||||
self.shell.navigation.selected_nav = NavItem::Workspace;
|
||||
let request_to_ui_duration = requested_at
|
||||
.map(|requested_at| requested_at.elapsed())
|
||||
.unwrap_or(worker_duration + worker_to_ui_duration);
|
||||
|
||||
@@ -80,8 +80,8 @@ impl NyaTermApp {
|
||||
if self.saved_connection_start_is_pending_or_queued(&connection) {
|
||||
self.terminal.view.status =
|
||||
format!("{} is already connecting or queued", connection.name);
|
||||
self.selected_nav = NavItem::Workspace;
|
||||
self.main_mode = MainMode::Workspace;
|
||||
self.shell.navigation.selected_nav = NavItem::Workspace;
|
||||
self.shell.navigation.main_mode = MainMode::Workspace;
|
||||
cx.notify();
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -244,10 +244,7 @@ impl NyaTermApp {
|
||||
pub(in crate::features) fn activate_session_id(&mut self, session_id: &str) -> Option<String> {
|
||||
self.session.start.active_pending = None;
|
||||
self.session.start.active_failed = None;
|
||||
self.open_tabs_menu_open = false;
|
||||
self.new_session_menu_open = false;
|
||||
self.new_session_all_sessions_open = false;
|
||||
self.new_session_group_menu_path.clear();
|
||||
self.shell.chrome.prepare_session_switch();
|
||||
// Session switch resets terminal-output credential autofill (Tauri XTerminal remount).
|
||||
self.terminal.assist.reset_for_session_switch();
|
||||
let previous_session_id = self.session.active_id.clone();
|
||||
@@ -265,7 +262,6 @@ impl NyaTermApp {
|
||||
self.transfer.queue.job_delete = None;
|
||||
self.reset_remote_runtime_for_session_switch();
|
||||
}
|
||||
self.session_tab_scroll_into_view_pending = true;
|
||||
// Keep workspace_split mirrored to the active tab's per-tab pane root.
|
||||
self.sync_workspace_split_from_active_tab();
|
||||
self.transfer.browser.auto_sync_cwd_last_at = None;
|
||||
@@ -279,9 +275,9 @@ impl NyaTermApp {
|
||||
// Transfer browser state is only needed when the transfers panel is open
|
||||
// or we already have cached browser state for this session. Skipping the
|
||||
// full reset on every activate keeps connect/switch chrome responsive.
|
||||
let transfers_panel_visible = self.active_left_panel == Some(NavItem::Transfers)
|
||||
|| self.active_right_panel == Some(NavItem::Transfers)
|
||||
|| self.selected_nav == NavItem::Transfers;
|
||||
let transfers_panel_visible = self.shell.panels.active_left == Some(NavItem::Transfers)
|
||||
|| self.shell.panels.active_right == Some(NavItem::Transfers)
|
||||
|| self.shell.navigation.selected_nav == NavItem::Transfers;
|
||||
if transfers_panel_visible
|
||||
|| self.transfer.browser.session_cache.contains_key(session_id)
|
||||
|| !self.transfer.browser.entries.is_empty()
|
||||
@@ -435,7 +431,7 @@ impl NyaTermApp {
|
||||
} else {
|
||||
format!("active {}", short_id(&focus_id))
|
||||
};
|
||||
self.selected_nav = NavItem::Workspace;
|
||||
self.shell.navigation.selected_nav = NavItem::Workspace;
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
@@ -464,8 +460,8 @@ impl NyaTermApp {
|
||||
let next_index = (active_index as isize + offset).rem_euclid(len) as usize;
|
||||
let session_id = sessions[next_index].id.clone();
|
||||
self.activate_session_id_with_surface_sync(&session_id, cx);
|
||||
self.selected_nav = NavItem::Workspace;
|
||||
self.main_mode = MainMode::Workspace;
|
||||
self.shell.navigation.selected_nav = NavItem::Workspace;
|
||||
self.shell.navigation.main_mode = MainMode::Workspace;
|
||||
self.terminal.view.status = format!("active {}", short_id(&session_id));
|
||||
cx.notify();
|
||||
}
|
||||
@@ -484,49 +480,30 @@ impl NyaTermApp {
|
||||
let index = index.min(sessions.len().saturating_sub(1));
|
||||
let session_id = sessions[index].id.clone();
|
||||
self.activate_session_id_with_surface_sync(&session_id, cx);
|
||||
self.selected_nav = NavItem::Workspace;
|
||||
self.main_mode = MainMode::Workspace;
|
||||
self.shell.navigation.selected_nav = NavItem::Workspace;
|
||||
self.shell.navigation.main_mode = MainMode::Workspace;
|
||||
self.terminal.view.status = format!("active {}", short_id(&session_id));
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
pub(in crate::features) fn toggle_open_tabs_menu(&mut self, cx: &mut Context<Self>) {
|
||||
self.open_tabs_menu_open = !self.open_tabs_menu_open;
|
||||
if self.open_tabs_menu_open {
|
||||
self.new_session_menu_open = false;
|
||||
self.new_session_all_sessions_open = false;
|
||||
self.new_session_group_menu_path.clear();
|
||||
self.title_menu_open = None;
|
||||
}
|
||||
self.shell.chrome.toggle_open_tabs_menu();
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
pub(in crate::features) fn close_open_tabs_menu(&mut self, cx: &mut Context<Self>) {
|
||||
if self.open_tabs_menu_open {
|
||||
self.open_tabs_menu_open = false;
|
||||
if self.shell.chrome.close_open_tabs_menu() {
|
||||
cx.notify();
|
||||
}
|
||||
}
|
||||
|
||||
pub(in crate::features) fn toggle_new_session_menu(&mut self, cx: &mut Context<Self>) {
|
||||
self.new_session_menu_open = !self.new_session_menu_open;
|
||||
if self.new_session_menu_open {
|
||||
self.open_tabs_menu_open = false;
|
||||
self.title_menu_open = None;
|
||||
}
|
||||
self.new_session_all_sessions_open = false;
|
||||
self.new_session_group_menu_path.clear();
|
||||
self.shell.chrome.toggle_new_session_menu();
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
pub(in crate::features) fn close_new_session_menu(&mut self, cx: &mut Context<Self>) {
|
||||
let changed = self.new_session_menu_open
|
||||
|| self.new_session_all_sessions_open
|
||||
|| !self.new_session_group_menu_path.is_empty();
|
||||
self.new_session_menu_open = false;
|
||||
self.new_session_all_sessions_open = false;
|
||||
self.new_session_group_menu_path.clear();
|
||||
if changed {
|
||||
if self.shell.chrome.close_new_session_menu() {
|
||||
cx.notify();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -198,7 +198,7 @@ impl NyaTermApp {
|
||||
.into_iter()
|
||||
.map(|session| {
|
||||
let mut tab = self.serialize_open_tab_for_session(&session);
|
||||
if let Some(root) = self.session_pane_roots.get(&session.id) {
|
||||
if let Some(root) = self.shell.workspace.pane_roots.get(&session.id) {
|
||||
if root.is_split() {
|
||||
if let Some(pane_root) = self.workspace_pane_to_restorable_pane(root) {
|
||||
tab.root = Some(pane_root);
|
||||
@@ -241,14 +241,16 @@ impl NyaTermApp {
|
||||
/// entry whose `root` is a Tauri RestorablePaneNode tree (for interop).
|
||||
fn serialize_open_tabs_as_single_pane_tab(&self) -> Option<Vec<RestorableOpenTab>> {
|
||||
// Only collapse to one open_tabs entry when exactly one split tree covers every session.
|
||||
if self.session_pane_roots.len() > 1 {
|
||||
if self.shell.workspace.pane_roots.len() > 1 {
|
||||
return None;
|
||||
}
|
||||
let root = self
|
||||
.session_pane_roots
|
||||
.shell
|
||||
.workspace
|
||||
.pane_roots
|
||||
.values()
|
||||
.find(|root| root.is_split())
|
||||
.or(self.workspace_split.as_ref())?;
|
||||
.or(self.shell.workspace.split.as_ref())?;
|
||||
if !root.is_split() {
|
||||
return None;
|
||||
}
|
||||
@@ -464,7 +466,7 @@ impl NyaTermApp {
|
||||
self.mark_startup_restore_complete(cx);
|
||||
// After all tabs reconnect, attempt multi-leaf then global pane layout restore.
|
||||
self.terminal.windows.restored = false;
|
||||
self.workspace_pane_layout_restored = false;
|
||||
self.shell.workspace.pane_layout_restored = false;
|
||||
self.try_restore_terminal_window_layout();
|
||||
// Prefer stored ui.workspace_pane_layout only when no open_tabs per-tab roots exist.
|
||||
// open_tabs[].root maps to per-tab session_pane_roots (Tauri Tab.root).
|
||||
@@ -479,7 +481,7 @@ impl NyaTermApp {
|
||||
if pending_layouts.is_empty() {
|
||||
self.try_restore_workspace_pane_layout();
|
||||
} else {
|
||||
self.workspace_pane_layout_restored = true;
|
||||
self.shell.workspace.pane_layout_restored = true;
|
||||
for layout in pending_layouts {
|
||||
self.apply_restorable_workspace_pane_layout(layout);
|
||||
}
|
||||
@@ -498,9 +500,11 @@ impl NyaTermApp {
|
||||
}
|
||||
if self.terminal_windows_is_multi_leaf() {
|
||||
self.terminal.view.status = "restored workspace tabs and window layout".to_string();
|
||||
} else if !self.session_pane_roots.is_empty()
|
||||
} else if !self.shell.workspace.pane_roots.is_empty()
|
||||
|| self
|
||||
.workspace_split
|
||||
.shell
|
||||
.workspace
|
||||
.split
|
||||
.as_ref()
|
||||
.is_some_and(|root| root.is_split())
|
||||
{
|
||||
@@ -600,20 +604,23 @@ impl NyaTermApp {
|
||||
return;
|
||||
};
|
||||
// Avoid clobbering an existing distinct per-tab tree for the same root.
|
||||
if let Some(existing) = self.session_pane_roots.get(&first) {
|
||||
if let Some(existing) = self.shell.workspace.pane_roots.get(&first) {
|
||||
if existing != &restored {
|
||||
// Prefer the newly restored tree from open_tabs for this root.
|
||||
}
|
||||
}
|
||||
self.session_pane_roots.insert(first.clone(), restored);
|
||||
self.shell
|
||||
.workspace
|
||||
.pane_roots
|
||||
.insert(first.clone(), restored);
|
||||
self.rebuild_session_tab_owners();
|
||||
if self.session.active_id.is_none() {
|
||||
self.session.active_id = Some(first);
|
||||
}
|
||||
self.sync_workspace_split_from_active_tab();
|
||||
self.workspace_pane_layout_restored = true;
|
||||
self.selected_nav = NavItem::Workspace;
|
||||
self.main_mode = MainMode::Workspace;
|
||||
self.shell.workspace.pane_layout_restored = true;
|
||||
self.shell.navigation.selected_nav = NavItem::Workspace;
|
||||
self.shell.navigation.main_mode = MainMode::Workspace;
|
||||
self.terminal.view.status = "restored pane layout from open_tabs root".to_string();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -76,7 +76,7 @@ impl NyaTermApp {
|
||||
) {
|
||||
self.security.unlock.master_required_prompt_open = false;
|
||||
self.security.unlock.pending_action = None;
|
||||
self.settings_active_tab = SettingsTab::Security;
|
||||
self.shell.navigation.settings.active_tab = SettingsTab::Security;
|
||||
self.open_page(NavItem::Settings, cx);
|
||||
}
|
||||
|
||||
|
||||
@@ -8,10 +8,10 @@ use crate::models::{CloudSyncSecretDraft, MainMode, NavItem, TranslationSecretDr
|
||||
|
||||
impl NyaTermApp {
|
||||
pub(in crate::features) fn begin_settings_draft(&mut self) {
|
||||
if self.settings_draft_snapshot.is_some() {
|
||||
if self.shell.navigation.settings.draft_snapshot.is_some() {
|
||||
return;
|
||||
}
|
||||
self.settings_draft_snapshot = Some(SettingsDraftSnapshot {
|
||||
self.shell.navigation.settings.draft_snapshot = Some(SettingsDraftSnapshot {
|
||||
settings: self.settings.clone(),
|
||||
ai_settings: self.ai.settings.config.clone(),
|
||||
ai_model_draft: self.ai.settings.model_draft.clone(),
|
||||
@@ -28,7 +28,7 @@ impl NyaTermApp {
|
||||
}
|
||||
|
||||
pub(in crate::features) fn settings_draft_dirty(&self) -> bool {
|
||||
let Some(snapshot) = self.settings_draft_snapshot.as_ref() else {
|
||||
let Some(snapshot) = self.shell.navigation.settings.draft_snapshot.as_ref() else {
|
||||
return false;
|
||||
};
|
||||
snapshot.settings != self.settings
|
||||
@@ -50,7 +50,7 @@ impl NyaTermApp {
|
||||
&mut self,
|
||||
cx: &mut Context<Self>,
|
||||
) -> bool {
|
||||
if self.settings_draft_snapshot.is_none() {
|
||||
if self.shell.navigation.settings.draft_snapshot.is_none() {
|
||||
return false;
|
||||
}
|
||||
self.store_status.message = "settings draft changed".to_string();
|
||||
@@ -241,10 +241,10 @@ impl NyaTermApp {
|
||||
}
|
||||
|
||||
pub(in crate::features) fn rebase_open_settings_draft(&mut self) {
|
||||
if self.settings_draft_snapshot.is_none() {
|
||||
if self.shell.navigation.settings.draft_snapshot.is_none() {
|
||||
return;
|
||||
}
|
||||
self.settings_draft_snapshot = None;
|
||||
self.shell.navigation.settings.draft_snapshot = None;
|
||||
self.settings_master_password_enabled = self.settings.has_master_password;
|
||||
self.settings_master_password_draft.clear();
|
||||
self.begin_settings_draft();
|
||||
@@ -255,7 +255,7 @@ impl NyaTermApp {
|
||||
close_after_apply: bool,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
if self.settings_draft_snapshot.is_none() {
|
||||
if self.shell.navigation.settings.draft_snapshot.is_none() {
|
||||
if close_after_apply {
|
||||
self.finish_settings_page(cx);
|
||||
}
|
||||
@@ -357,7 +357,7 @@ impl NyaTermApp {
|
||||
store.save_workspace_pane_layout(None)
|
||||
});
|
||||
}
|
||||
self.settings_draft_snapshot = None;
|
||||
self.shell.navigation.settings.draft_snapshot = None;
|
||||
self.store_status.message = "settings applied".to_string();
|
||||
self.store_status.ready = true;
|
||||
self.terminal.view.status = "settings applied".to_string();
|
||||
@@ -378,7 +378,7 @@ impl NyaTermApp {
|
||||
}
|
||||
|
||||
pub(in crate::features) fn cancel_settings(&mut self, cx: &mut Context<Self>) {
|
||||
if let Some(snapshot) = self.settings_draft_snapshot.take() {
|
||||
if let Some(snapshot) = self.shell.navigation.settings.draft_snapshot.take() {
|
||||
self.apply_gpui_settings(snapshot.settings);
|
||||
self.ai.settings.config = snapshot.ai_settings;
|
||||
self.ai.settings.model_draft = snapshot.ai_model_draft;
|
||||
@@ -410,7 +410,7 @@ impl NyaTermApp {
|
||||
if self.settings_draft_dirty() {
|
||||
self.apply_settings_draft(true, cx);
|
||||
} else {
|
||||
self.settings_draft_snapshot = None;
|
||||
self.shell.navigation.settings.draft_snapshot = None;
|
||||
self.finish_settings_page(cx);
|
||||
}
|
||||
}
|
||||
@@ -454,22 +454,30 @@ impl NyaTermApp {
|
||||
self.forget_text_inputs("ai.settings.action.");
|
||||
self.forget_text_inputs("ai.settings.manual-model.");
|
||||
self.forget_text_inputs("keyword.highlight.");
|
||||
self.settings_window = None;
|
||||
self.settings_window_open_pending = false;
|
||||
if self.main_mode == MainMode::Page && self.selected_nav == NavItem::Settings {
|
||||
self.main_mode = MainMode::Workspace;
|
||||
self.left_sidebar_collapsed = self
|
||||
.settings_previous_left_collapsed
|
||||
self.shell.navigation.settings.window = None;
|
||||
self.shell.navigation.settings.window_open_pending = false;
|
||||
if self.shell.navigation.main_mode == MainMode::Page
|
||||
&& self.shell.navigation.selected_nav == NavItem::Settings
|
||||
{
|
||||
self.shell.navigation.main_mode = MainMode::Workspace;
|
||||
self.shell.panels.left_collapsed = self
|
||||
.shell
|
||||
.navigation
|
||||
.settings
|
||||
.previous_left_collapsed
|
||||
.take()
|
||||
.unwrap_or_else(|| self.active_left_panel.is_none());
|
||||
self.right_inspector_collapsed = self
|
||||
.settings_previous_right_collapsed
|
||||
.unwrap_or_else(|| self.shell.panels.active_left.is_none());
|
||||
self.shell.panels.right_collapsed = self
|
||||
.shell
|
||||
.navigation
|
||||
.settings
|
||||
.previous_right_collapsed
|
||||
.take()
|
||||
.unwrap_or_else(|| self.active_right_panel.is_none());
|
||||
.unwrap_or_else(|| self.shell.panels.active_right.is_none());
|
||||
self.persist_ui_layout();
|
||||
} else {
|
||||
self.settings_previous_left_collapsed = None;
|
||||
self.settings_previous_right_collapsed = None;
|
||||
self.shell.navigation.settings.previous_left_collapsed = None;
|
||||
self.shell.navigation.settings.previous_right_collapsed = None;
|
||||
}
|
||||
self.terminal.view.status = "settings closed".to_string();
|
||||
cx.notify();
|
||||
|
||||
+4
-2
@@ -24,7 +24,9 @@ impl NyaTermApp {
|
||||
) {
|
||||
self.settings.startup_restore_window_layout = !self.settings.startup_restore_window_layout;
|
||||
self.save_general_settings(cx);
|
||||
if !self.settings.startup_restore_window_layout && self.settings_draft_snapshot.is_none() {
|
||||
if !self.settings.startup_restore_window_layout
|
||||
&& self.shell.navigation.settings.draft_snapshot.is_none()
|
||||
{
|
||||
// Clear stored layouts when the user disables restore.
|
||||
let _ = ConnectionStore::open_with_portable_key_path(
|
||||
self.runtime.config_dir(),
|
||||
@@ -149,7 +151,7 @@ impl NyaTermApp {
|
||||
self.settings.interaction_command_suggestions_enabled =
|
||||
!self.settings.interaction_command_suggestions_enabled;
|
||||
if !self.settings.interaction_command_suggestions_enabled
|
||||
&& self.settings_draft_snapshot.is_none()
|
||||
&& self.shell.navigation.settings.draft_snapshot.is_none()
|
||||
{
|
||||
self.terminal.assist.clear_command_tracking();
|
||||
}
|
||||
|
||||
@@ -48,7 +48,7 @@ impl NyaTermApp {
|
||||
) {
|
||||
let next = (self.settings.terminal_scrollback_lines as i32 + delta).clamp(100, 100_000);
|
||||
self.settings.terminal_scrollback_lines = next as u32;
|
||||
if self.settings_draft_snapshot.is_none() {
|
||||
if self.shell.navigation.settings.draft_snapshot.is_none() {
|
||||
self.enforce_terminal_scrollback_limit();
|
||||
}
|
||||
self.save_terminal_settings(cx);
|
||||
|
||||
@@ -24,10 +24,18 @@ impl SettingsWindow {
|
||||
|
||||
impl Render for SettingsWindow {
|
||||
fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
||||
if self.app.read(cx).settings_draft_snapshot.is_none() {
|
||||
if self
|
||||
.app
|
||||
.read(cx)
|
||||
.shell
|
||||
.navigation
|
||||
.settings
|
||||
.draft_snapshot
|
||||
.is_none()
|
||||
{
|
||||
self.app.update(cx, |app, cx| {
|
||||
app.settings_window = None;
|
||||
app.settings_window_open_pending = false;
|
||||
app.shell.navigation.settings.window = None;
|
||||
app.shell.navigation.settings.window_open_pending = false;
|
||||
cx.notify();
|
||||
});
|
||||
window.defer(cx, |window, _| window.remove_window());
|
||||
@@ -77,7 +85,7 @@ impl Render for SettingsWindow {
|
||||
|
||||
impl NyaTermApp {
|
||||
pub(in crate::features) fn activate_settings_window(&mut self, cx: &mut Context<Self>) -> bool {
|
||||
let Some(handle) = self.settings_window else {
|
||||
let Some(handle) = self.shell.navigation.settings.window else {
|
||||
return false;
|
||||
};
|
||||
let app = cx.entity();
|
||||
@@ -87,8 +95,14 @@ impl NyaTermApp {
|
||||
.is_err()
|
||||
{
|
||||
let _ = app.update(cx, |app, cx| {
|
||||
if app.settings_window.is_some_and(|current| current == handle) {
|
||||
app.settings_window = None;
|
||||
if app
|
||||
.shell
|
||||
.navigation
|
||||
.settings
|
||||
.window
|
||||
.is_some_and(|current| current == handle)
|
||||
{
|
||||
app.shell.navigation.settings.window = None;
|
||||
cx.notify();
|
||||
}
|
||||
});
|
||||
@@ -101,15 +115,15 @@ impl NyaTermApp {
|
||||
if self.activate_settings_window(cx) {
|
||||
return true;
|
||||
}
|
||||
if self.settings_window_open_pending {
|
||||
if self.shell.navigation.settings.window_open_pending {
|
||||
return true;
|
||||
}
|
||||
|
||||
self.settings_window_open_pending = true;
|
||||
self.shell.navigation.settings.window_open_pending = true;
|
||||
cx.notify();
|
||||
let app = cx.entity();
|
||||
cx.defer(move |cx| {
|
||||
let should_open = app.read(cx).settings_window_open_pending;
|
||||
let should_open = app.read(cx).shell.navigation.settings.window_open_pending;
|
||||
if should_open {
|
||||
open_settings_window_now_from_app(app, cx);
|
||||
}
|
||||
@@ -119,17 +133,24 @@ impl NyaTermApp {
|
||||
}
|
||||
|
||||
fn open_settings_window_now_from_app(app: Entity<NyaTermApp>, cx: &mut App) {
|
||||
if app.read(cx).settings_window.is_some() {
|
||||
if app.read(cx).shell.navigation.settings.window.is_some() {
|
||||
let _ = app.update(cx, |app, cx| {
|
||||
app.settings_window_open_pending = false;
|
||||
app.shell.navigation.settings.window_open_pending = false;
|
||||
app.activate_settings_window(cx);
|
||||
cx.notify();
|
||||
});
|
||||
return;
|
||||
}
|
||||
if app.read(cx).settings_draft_snapshot.is_none() {
|
||||
if app
|
||||
.read(cx)
|
||||
.shell
|
||||
.navigation
|
||||
.settings
|
||||
.draft_snapshot
|
||||
.is_none()
|
||||
{
|
||||
let _ = app.update(cx, |app, cx| {
|
||||
app.settings_window_open_pending = false;
|
||||
app.shell.navigation.settings.window_open_pending = false;
|
||||
cx.notify();
|
||||
});
|
||||
return;
|
||||
@@ -152,8 +173,8 @@ fn open_settings_window_now_from_app(app: Entity<NyaTermApp>, cx: &mut App) {
|
||||
window.on_window_should_close(cx, move |_, cx| {
|
||||
close_app.update(cx, |app, cx| {
|
||||
app.cancel_settings(cx);
|
||||
app.settings_window = None;
|
||||
app.settings_window_open_pending = false;
|
||||
app.shell.navigation.settings.window = None;
|
||||
app.shell.navigation.settings.window_open_pending = false;
|
||||
});
|
||||
true
|
||||
});
|
||||
@@ -163,19 +184,19 @@ fn open_settings_window_now_from_app(app: Entity<NyaTermApp>, cx: &mut App) {
|
||||
|
||||
let _ = app.update(cx, |app, cx| match result {
|
||||
Ok(handle) => {
|
||||
app.settings_window = Some(handle);
|
||||
app.settings_window_open_pending = false;
|
||||
app.settings_previous_left_collapsed = None;
|
||||
app.settings_previous_right_collapsed = None;
|
||||
app.shell.navigation.settings.window = Some(handle);
|
||||
app.shell.navigation.settings.window_open_pending = false;
|
||||
app.shell.navigation.settings.previous_left_collapsed = None;
|
||||
app.shell.navigation.settings.previous_right_collapsed = None;
|
||||
cx.notify();
|
||||
}
|
||||
Err(error) => {
|
||||
app.settings_window = None;
|
||||
app.settings_window_open_pending = false;
|
||||
app.main_mode = MainMode::Page;
|
||||
app.selected_nav = crate::models::NavItem::Settings;
|
||||
app.left_sidebar_collapsed = true;
|
||||
app.right_inspector_collapsed = true;
|
||||
app.shell.navigation.settings.window = None;
|
||||
app.shell.navigation.settings.window_open_pending = false;
|
||||
app.shell.navigation.main_mode = MainMode::Page;
|
||||
app.shell.navigation.selected_nav = crate::models::NavItem::Settings;
|
||||
app.shell.panels.left_collapsed = true;
|
||||
app.shell.panels.right_collapsed = true;
|
||||
app.terminal.view.status = format!("failed to open settings window: {error}");
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
@@ -73,14 +73,16 @@ impl NyaTermApp {
|
||||
};
|
||||
zones
|
||||
.into_iter()
|
||||
.any(|zone| !self.activity_bar_layout.zone(zone).is_empty())
|
||||
.any(|zone| !self.shell.chrome.activity_bar_layout.zone(zone).is_empty())
|
||||
}
|
||||
|
||||
pub(in crate::features) fn activity_entries_for_zone(
|
||||
&self,
|
||||
zone: ActivityBarZone,
|
||||
) -> Vec<ActivityBarEntry> {
|
||||
self.activity_bar_layout
|
||||
self.shell
|
||||
.chrome
|
||||
.activity_bar_layout
|
||||
.zone(zone)
|
||||
.iter()
|
||||
.filter_map(|id| ActivityBarEntry::from_persistence_id(id))
|
||||
@@ -88,15 +90,20 @@ impl NyaTermApp {
|
||||
}
|
||||
|
||||
pub(in crate::features) fn sync_activity_layout_to_settings(&mut self) {
|
||||
self.settings.ui_activity_bar_left_top = self.activity_bar_layout.left_top.clone();
|
||||
self.settings.ui_activity_bar_left_bottom = self.activity_bar_layout.left_bottom.clone();
|
||||
self.settings.ui_activity_bar_right_top = self.activity_bar_layout.right_top.clone();
|
||||
self.settings.ui_activity_bar_right_bottom = self.activity_bar_layout.right_bottom.clone();
|
||||
self.settings.ui_activity_bar_show_labels = self.activity_bar_layout.show_labels;
|
||||
self.settings.ui_activity_bar_left_top =
|
||||
self.shell.chrome.activity_bar_layout.left_top.clone();
|
||||
self.settings.ui_activity_bar_left_bottom =
|
||||
self.shell.chrome.activity_bar_layout.left_bottom.clone();
|
||||
self.settings.ui_activity_bar_right_top =
|
||||
self.shell.chrome.activity_bar_layout.right_top.clone();
|
||||
self.settings.ui_activity_bar_right_bottom =
|
||||
self.shell.chrome.activity_bar_layout.right_bottom.clone();
|
||||
self.settings.ui_activity_bar_show_labels =
|
||||
self.shell.chrome.activity_bar_layout.show_labels;
|
||||
}
|
||||
|
||||
pub(in crate::features) fn apply_activity_layout_from_settings(&mut self) {
|
||||
self.activity_bar_layout = ActivityBarLayoutState {
|
||||
self.shell.chrome.activity_bar_layout = ActivityBarLayoutState {
|
||||
left_top: self.settings.ui_activity_bar_left_top.clone(),
|
||||
left_bottom: self.settings.ui_activity_bar_left_bottom.clone(),
|
||||
right_top: self.settings.ui_activity_bar_right_top.clone(),
|
||||
@@ -110,7 +117,14 @@ impl NyaTermApp {
|
||||
let mut seen = std::collections::HashSet::new();
|
||||
for zone in ActivityBarZone::all() {
|
||||
let mut next = Vec::new();
|
||||
for id in self.activity_bar_layout.zone(zone).iter().cloned() {
|
||||
for id in self
|
||||
.shell
|
||||
.chrome
|
||||
.activity_bar_layout
|
||||
.zone(zone)
|
||||
.iter()
|
||||
.cloned()
|
||||
{
|
||||
if id == "fileTransfer" {
|
||||
continue;
|
||||
}
|
||||
@@ -118,7 +132,7 @@ impl NyaTermApp {
|
||||
next.push(id);
|
||||
}
|
||||
}
|
||||
*self.activity_bar_layout.zone_mut(zone) = next;
|
||||
*self.shell.chrome.activity_bar_layout.zone_mut(zone) = next;
|
||||
}
|
||||
// Keep intentionally empty zones empty. Tauri only restores missing entries;
|
||||
// it does not repopulate a zone after the user moves its last item away.
|
||||
@@ -131,15 +145,20 @@ impl NyaTermApp {
|
||||
.cloned()
|
||||
.collect::<Vec<_>>();
|
||||
if !missing.is_empty() {
|
||||
self.activity_bar_layout.zone_mut(zone).extend(missing);
|
||||
self.shell
|
||||
.chrome
|
||||
.activity_bar_layout
|
||||
.zone_mut(zone)
|
||||
.extend(missing);
|
||||
seen.extend(defaults.zone(zone).iter().cloned());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(in crate::features) fn toggle_activity_bar_labels(&mut self, cx: &mut Context<Self>) {
|
||||
self.activity_bar_layout.show_labels = !self.activity_bar_layout.show_labels;
|
||||
self.terminal.view.status = if self.activity_bar_layout.show_labels {
|
||||
self.shell.chrome.activity_bar_layout.show_labels =
|
||||
!self.shell.chrome.activity_bar_layout.show_labels;
|
||||
self.terminal.view.status = if self.shell.chrome.activity_bar_layout.show_labels {
|
||||
"activity labels shown".to_string()
|
||||
} else {
|
||||
"activity labels hidden".to_string()
|
||||
@@ -156,8 +175,8 @@ impl NyaTermApp {
|
||||
event: &MouseDownEvent,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
self.title_menu_open = None;
|
||||
self.activity_bar_context_menu = Some(ActivityBarContextMenuState {
|
||||
self.shell.chrome.title_menu_open = None;
|
||||
self.shell.chrome.activity_bar_context_menu = Some(ActivityBarContextMenuState {
|
||||
entry_id,
|
||||
zone,
|
||||
index,
|
||||
@@ -169,7 +188,7 @@ impl NyaTermApp {
|
||||
}
|
||||
|
||||
pub(in crate::features) fn open_activity_bar_move_submenu(&mut self, cx: &mut Context<Self>) {
|
||||
let Some(menu) = self.activity_bar_context_menu.as_mut() else {
|
||||
let Some(menu) = self.shell.chrome.activity_bar_context_menu.as_mut() else {
|
||||
return;
|
||||
};
|
||||
if !menu.move_submenu_open {
|
||||
@@ -179,7 +198,7 @@ impl NyaTermApp {
|
||||
}
|
||||
|
||||
pub(in crate::features) fn close_activity_bar_context_menu(&mut self, cx: &mut Context<Self>) {
|
||||
self.activity_bar_context_menu = None;
|
||||
self.shell.chrome.activity_bar_context_menu = None;
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
@@ -190,7 +209,8 @@ impl NyaTermApp {
|
||||
target_index: Option<usize>,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
let Some((source_zone, source_index)) = self.activity_bar_layout.find_entry(&entry_id)
|
||||
let Some((source_zone, source_index)) =
|
||||
self.shell.chrome.activity_bar_layout.find_entry(&entry_id)
|
||||
else {
|
||||
self.terminal.view.status = "activity item not found".to_string();
|
||||
cx.notify();
|
||||
@@ -199,14 +219,19 @@ impl NyaTermApp {
|
||||
|
||||
// Same entry dropped on itself — no-op.
|
||||
if source_zone == target_zone {
|
||||
let len = self.activity_bar_layout.zone(target_zone).len();
|
||||
let len = self
|
||||
.shell
|
||||
.chrome
|
||||
.activity_bar_layout
|
||||
.zone(target_zone)
|
||||
.len();
|
||||
let mut insert_at = target_index.unwrap_or(len);
|
||||
if source_index < insert_at {
|
||||
insert_at = insert_at.saturating_sub(1);
|
||||
}
|
||||
insert_at = insert_at.min(len.saturating_sub(1));
|
||||
if insert_at == source_index {
|
||||
self.activity_bar_context_menu = None;
|
||||
self.shell.chrome.activity_bar_context_menu = None;
|
||||
cx.notify();
|
||||
return;
|
||||
}
|
||||
@@ -214,16 +239,31 @@ impl NyaTermApp {
|
||||
|
||||
// Remove from source.
|
||||
let removed = self
|
||||
.shell
|
||||
.chrome
|
||||
.activity_bar_layout
|
||||
.zone_mut(source_zone)
|
||||
.remove(source_index);
|
||||
let mut insert_at =
|
||||
target_index.unwrap_or_else(|| self.activity_bar_layout.zone(target_zone).len());
|
||||
let mut insert_at = target_index.unwrap_or_else(|| {
|
||||
self.shell
|
||||
.chrome
|
||||
.activity_bar_layout
|
||||
.zone(target_zone)
|
||||
.len()
|
||||
});
|
||||
if source_zone == target_zone && source_index < insert_at {
|
||||
insert_at = insert_at.saturating_sub(1);
|
||||
}
|
||||
insert_at = insert_at.min(self.activity_bar_layout.zone(target_zone).len());
|
||||
self.activity_bar_layout
|
||||
insert_at = insert_at.min(
|
||||
self.shell
|
||||
.chrome
|
||||
.activity_bar_layout
|
||||
.zone(target_zone)
|
||||
.len(),
|
||||
);
|
||||
self.shell
|
||||
.chrome
|
||||
.activity_bar_layout
|
||||
.zone_mut(target_zone)
|
||||
.insert(insert_at, removed);
|
||||
|
||||
@@ -234,7 +274,7 @@ impl NyaTermApp {
|
||||
self.clear_activity_entry_from_side(&entry_id, source_side);
|
||||
}
|
||||
|
||||
self.activity_bar_context_menu = None;
|
||||
self.shell.chrome.activity_bar_context_menu = None;
|
||||
self.terminal.view.status = format!(
|
||||
"moved {} to {}",
|
||||
entry_id,
|
||||
@@ -254,27 +294,34 @@ impl NyaTermApp {
|
||||
fn clear_activity_entry_from_side(&mut self, entry_id: &str, side: PanelSide) {
|
||||
match side {
|
||||
PanelSide::Left => {
|
||||
self.left_open_panels.retain(|id| id != entry_id);
|
||||
self.shell.panels.left_open.retain(|id| id != entry_id);
|
||||
if self
|
||||
.active_left_panel
|
||||
.shell
|
||||
.panels
|
||||
.active_left
|
||||
.is_some_and(|item| item.persistence_id() == entry_id)
|
||||
{
|
||||
self.active_left_panel = None;
|
||||
self.shell.panels.active_left = None;
|
||||
}
|
||||
if self.left_open_panels.is_empty() && self.active_left_panel.is_none() {
|
||||
self.left_sidebar_collapsed = true;
|
||||
if self.shell.panels.left_open.is_empty() && self.shell.panels.active_left.is_none()
|
||||
{
|
||||
self.shell.panels.left_collapsed = true;
|
||||
}
|
||||
}
|
||||
PanelSide::Right => {
|
||||
self.right_open_panels.retain(|id| id != entry_id);
|
||||
self.shell.panels.right_open.retain(|id| id != entry_id);
|
||||
if self
|
||||
.active_right_panel
|
||||
.shell
|
||||
.panels
|
||||
.active_right
|
||||
.is_some_and(|item| item.persistence_id() == entry_id)
|
||||
{
|
||||
self.active_right_panel = None;
|
||||
self.shell.panels.active_right = None;
|
||||
}
|
||||
if self.right_open_panels.is_empty() && self.active_right_panel.is_none() {
|
||||
self.right_inspector_collapsed = true;
|
||||
if self.shell.panels.right_open.is_empty()
|
||||
&& self.shell.panels.active_right.is_none()
|
||||
{
|
||||
self.shell.panels.right_collapsed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -293,11 +340,11 @@ impl NyaTermApp {
|
||||
self.open_panel(item, cx);
|
||||
if !cfg!(target_os = "macos") {
|
||||
match side {
|
||||
Some(PanelSide::Left) if self.last_viewport_size.0 < 1024. => {
|
||||
self.mobile_left_open = true;
|
||||
Some(PanelSide::Left) if self.shell.viewport.size.0 < 1024. => {
|
||||
self.shell.panels.mobile_left_open = true;
|
||||
}
|
||||
Some(PanelSide::Right) if self.last_viewport_size.0 < 768. => {
|
||||
self.mobile_right_open = true;
|
||||
Some(PanelSide::Right) if self.shell.viewport.size.0 < 768. => {
|
||||
self.shell.panels.mobile_right_open = true;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
@@ -327,11 +374,11 @@ impl NyaTermApp {
|
||||
self.open_panel(NavItem::Recording, cx);
|
||||
if !cfg!(target_os = "macos") {
|
||||
match side {
|
||||
Some(PanelSide::Left) if self.last_viewport_size.0 < 1024. => {
|
||||
self.mobile_left_open = true;
|
||||
Some(PanelSide::Left) if self.shell.viewport.size.0 < 1024. => {
|
||||
self.shell.panels.mobile_left_open = true;
|
||||
}
|
||||
Some(PanelSide::Right) if self.last_viewport_size.0 < 768. => {
|
||||
self.mobile_right_open = true;
|
||||
Some(PanelSide::Right) if self.shell.viewport.size.0 < 768. => {
|
||||
self.shell.panels.mobile_right_open = true;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
@@ -345,7 +392,8 @@ impl NyaTermApp {
|
||||
pub(in crate::features) fn activity_entry_selected(&self, entry: ActivityBarEntry) -> bool {
|
||||
match entry {
|
||||
ActivityBarEntry::Panel(NavItem::Settings) => {
|
||||
self.settings_window.is_some() || self.main_mode == MainMode::Page
|
||||
self.shell.navigation.settings.window.is_some()
|
||||
|| self.shell.navigation.main_mode == MainMode::Page
|
||||
}
|
||||
ActivityBarEntry::Panel(item) => self.panel_entry_selected(item),
|
||||
ActivityBarEntry::QuickCommands => {
|
||||
@@ -365,13 +413,13 @@ impl NyaTermApp {
|
||||
let Some(side) = self.panel_side_for_item(item) else {
|
||||
return false;
|
||||
};
|
||||
if self.panel_multi_open {
|
||||
if self.shell.panels.multi_open {
|
||||
let id = item.persistence_id();
|
||||
self.side_open_panel_ids(side).iter().any(|open| open == id)
|
||||
|| self.side_overlay_panel(side) == Some(item)
|
||||
|| match side {
|
||||
PanelSide::Left => self.active_left_panel == Some(item),
|
||||
PanelSide::Right => self.active_right_panel == Some(item),
|
||||
PanelSide::Left => self.shell.panels.active_left == Some(item),
|
||||
PanelSide::Right => self.shell.panels.active_right == Some(item),
|
||||
}
|
||||
} else {
|
||||
match side {
|
||||
|
||||
@@ -174,10 +174,6 @@ pub(super) fn window_geometry_churn_active(
|
||||
})
|
||||
}
|
||||
|
||||
pub(super) fn title_drag_active(title_drag_active_until: Option<Instant>, now: Instant) -> bool {
|
||||
title_drag_active_until.is_some_and(|until| now < until)
|
||||
}
|
||||
|
||||
pub(super) fn connect_settle_active(until: Option<Instant>, now: Instant) -> bool {
|
||||
until.is_some_and(|until| now < until)
|
||||
}
|
||||
@@ -439,7 +435,7 @@ mod tests {
|
||||
terminal_input_idle_remaining_delay, terminal_log_plain_text,
|
||||
terminal_output_dropped_marker, terminal_performance_tick_session_ids,
|
||||
terminal_render_work_pressure_active, terminal_user_scroll_frame_apply_pending,
|
||||
title_drag_active, viewport_change_terminal_session_ids, window_geometry_churn_active,
|
||||
viewport_change_terminal_session_ids, window_geometry_churn_active,
|
||||
};
|
||||
|
||||
#[test]
|
||||
@@ -545,14 +541,6 @@ mod tests {
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn title_drag_active_until_deadline() {
|
||||
let now = Instant::now();
|
||||
assert!(!title_drag_active(None, now));
|
||||
assert!(title_drag_active(Some(now + Duration::from_millis(1)), now));
|
||||
assert!(!title_drag_active(Some(now), now));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn connect_settle_active_until_deadline() {
|
||||
let now = Instant::now();
|
||||
|
||||
@@ -10,7 +10,7 @@ use crate::features::shell::event_pump::helpers::{
|
||||
connect_settle_active, connect_settle_deadline, diagnostic_log_due,
|
||||
pending_session_status_message, remote_refresh_due, runtime_output_pressure_active_from_counts,
|
||||
runtime_tick_interval_for_pressure, runtime_ui_notify_allowed,
|
||||
terminal_cell_metrics_refresh_needed, terminal_input_idle_remaining_delay, title_drag_active,
|
||||
terminal_cell_metrics_refresh_needed, terminal_input_idle_remaining_delay,
|
||||
viewport_change_terminal_session_ids, window_geometry_churn_active,
|
||||
};
|
||||
use crate::features::{
|
||||
@@ -59,13 +59,14 @@ impl NyaTermApp {
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) -> bool {
|
||||
let before_viewport = self.last_viewport_size;
|
||||
let before_metrics = self.terminal.layout.cell_metrics;
|
||||
let vs = window.viewport_size();
|
||||
self.last_viewport_size = (f32::from(vs.width), f32::from(vs.height));
|
||||
if self.last_viewport_size != before_viewport {
|
||||
let viewport_changed = self
|
||||
.shell
|
||||
.viewport
|
||||
.update_size((f32::from(vs.width), f32::from(vs.height)), Instant::now());
|
||||
if viewport_changed {
|
||||
// Geometry churn (resize / some window managers during move).
|
||||
self.last_viewport_change_at = Some(Instant::now());
|
||||
self.notify_terminal_surfaces_for_viewport_change(cx);
|
||||
}
|
||||
if terminal_cell_metrics_refresh_needed(self.terminal.layout.cell_metrics) {
|
||||
@@ -76,8 +77,7 @@ impl NyaTermApp {
|
||||
self.resize_all_known_terminal_surfaces();
|
||||
self.refresh_visible_terminal_surfaces(cx);
|
||||
}
|
||||
self.last_viewport_size != before_viewport
|
||||
|| self.terminal.layout.cell_metrics != before_metrics
|
||||
viewport_changed || self.terminal.layout.cell_metrics != before_metrics
|
||||
}
|
||||
|
||||
fn notify_terminal_surfaces_for_viewport_change(&mut self, cx: &mut Context<Self>) {
|
||||
@@ -219,11 +219,13 @@ impl NyaTermApp {
|
||||
}
|
||||
|
||||
pub(in crate::features) fn mark_title_drag_activity(&mut self) {
|
||||
self.title_drag_active_until = Some(Instant::now() + TITLE_DRAG_ACTIVE_HOLD);
|
||||
self.shell
|
||||
.viewport
|
||||
.mark_title_drag(Instant::now(), TITLE_DRAG_ACTIVE_HOLD);
|
||||
}
|
||||
|
||||
pub(in crate::features) fn title_drag_active(&self, now: Instant) -> bool {
|
||||
title_drag_active(self.title_drag_active_until, now)
|
||||
self.shell.viewport.title_drag_active(now)
|
||||
}
|
||||
|
||||
pub(in crate::features) fn should_log_slow_diagnostic(
|
||||
@@ -305,7 +307,7 @@ impl NyaTermApp {
|
||||
// idle cadence so full plane ticks do not stack on compositor paints.
|
||||
let now = Instant::now();
|
||||
if self.title_drag_active(now)
|
||||
|| window_geometry_churn_active(self.last_viewport_change_at, now)
|
||||
|| window_geometry_churn_active(self.shell.viewport.last_change_at, now)
|
||||
{
|
||||
return RUNTIME_IDLE_TICK_INTERVAL;
|
||||
}
|
||||
@@ -323,7 +325,7 @@ impl NyaTermApp {
|
||||
if !self.terminal.view.runtime.event_pump_started {
|
||||
return false;
|
||||
}
|
||||
if self.last_viewport_size != viewport_size
|
||||
if self.shell.viewport.size != viewport_size
|
||||
|| terminal_cell_metrics_refresh_needed(self.terminal.layout.cell_metrics)
|
||||
{
|
||||
return true;
|
||||
|
||||
@@ -161,7 +161,7 @@ impl NyaTermApp {
|
||||
}
|
||||
return true;
|
||||
}
|
||||
let geometry_churn = window_geometry_churn_active(self.last_viewport_change_at, now);
|
||||
let geometry_churn = window_geometry_churn_active(self.shell.viewport.last_change_at, now);
|
||||
let calm_tick = self.runtime_quiet_tick_allowed();
|
||||
if geometry_churn && calm_tick {
|
||||
dirty |= self.drive_pending_focus(window, cx);
|
||||
@@ -603,7 +603,7 @@ impl NyaTermApp {
|
||||
// Idle-plane work does not drain output; one pressure sample is enough for the stage.
|
||||
let output_pressure = self.runtime_output_pressure_active();
|
||||
let now = Instant::now();
|
||||
let geometry_churn = window_geometry_churn_active(self.last_viewport_change_at, now);
|
||||
let geometry_churn = window_geometry_churn_active(self.shell.viewport.last_change_at, now);
|
||||
let connect_settle =
|
||||
connect_settle_active(self.terminal.view.runtime.connect_settle_until, now);
|
||||
// Geometry churn / connect settle: keep focus only (no remote/layout/DB).
|
||||
|
||||
@@ -32,7 +32,7 @@ impl NyaTermApp {
|
||||
|
||||
// Dismiss strip menus with Escape (Tauri dropdown dismiss).
|
||||
if event.keystroke.key.as_str() == "escape"
|
||||
&& (self.open_tabs_menu_open || self.new_session_menu_open)
|
||||
&& (self.shell.chrome.open_tabs_menu_open || self.shell.chrome.new_session_menu_open)
|
||||
{
|
||||
self.close_open_tabs_menu(cx);
|
||||
self.close_new_session_menu(cx);
|
||||
|
||||
@@ -20,7 +20,7 @@ pub(in crate::features) use activity_bar_runtime::{
|
||||
pub(in crate::features) use appearance::{
|
||||
appearance_font_options, appearance_font_stack, gpui_code_font_family,
|
||||
};
|
||||
pub(in crate::features) use state::ShellFeatureState;
|
||||
pub(in crate::features) use state::{ShellFeatureInit, ShellFeatureState};
|
||||
pub(in crate::features) use tab_mouse::{
|
||||
ChromeTooltip, SessionTabDragPayload, SessionTabDragPreview, SessionTabTooltip,
|
||||
TAB_MOUSE_ACTIONS, TabMouseActionTarget,
|
||||
|
||||
@@ -7,23 +7,35 @@ impl NyaTermApp {
|
||||
pub(in crate::features) fn open_page(&mut self, item: NavItem, cx: &mut Context<Self>) {
|
||||
if item == NavItem::Settings || item.opens_settings() {
|
||||
self.begin_settings_draft();
|
||||
if let Some(group) = self.settings_active_tab.expandable_group_id() {
|
||||
self.settings_expanded_groups.insert(group.to_string());
|
||||
if let Some(group) = self
|
||||
.shell
|
||||
.navigation
|
||||
.settings
|
||||
.active_tab
|
||||
.expandable_group_id()
|
||||
{
|
||||
self.shell
|
||||
.navigation
|
||||
.settings
|
||||
.expanded_groups
|
||||
.insert(group.to_string());
|
||||
}
|
||||
if self.main_mode != MainMode::Page {
|
||||
self.settings_previous_left_collapsed = Some(self.left_sidebar_collapsed);
|
||||
self.settings_previous_right_collapsed = Some(self.right_inspector_collapsed);
|
||||
if self.shell.navigation.main_mode != MainMode::Page {
|
||||
self.shell.navigation.settings.previous_left_collapsed =
|
||||
Some(self.shell.panels.left_collapsed);
|
||||
self.shell.navigation.settings.previous_right_collapsed =
|
||||
Some(self.shell.panels.right_collapsed);
|
||||
}
|
||||
if self.open_settings_window(cx) {
|
||||
self.main_mode = MainMode::Workspace;
|
||||
self.shell.navigation.main_mode = MainMode::Workspace;
|
||||
self.terminal.view.status = "settings opened".to_string();
|
||||
cx.notify();
|
||||
return;
|
||||
}
|
||||
self.main_mode = MainMode::Page;
|
||||
self.selected_nav = NavItem::Settings;
|
||||
self.left_sidebar_collapsed = true;
|
||||
self.right_inspector_collapsed = true;
|
||||
self.shell.navigation.main_mode = MainMode::Page;
|
||||
self.shell.navigation.selected_nav = NavItem::Settings;
|
||||
self.shell.panels.left_collapsed = true;
|
||||
self.shell.panels.right_collapsed = true;
|
||||
self.terminal.view.status = "settings opened".to_string();
|
||||
cx.notify();
|
||||
return;
|
||||
@@ -43,14 +55,14 @@ impl NyaTermApp {
|
||||
return;
|
||||
}
|
||||
|
||||
if self.panel_multi_open && self.panel_side_for_item(item).is_some() {
|
||||
if self.shell.panels.multi_open && self.panel_side_for_item(item).is_some() {
|
||||
self.open_or_toggle_panel(item, cx);
|
||||
return;
|
||||
}
|
||||
|
||||
self.main_mode = MainMode::Workspace;
|
||||
self.selected_nav = item;
|
||||
self.right_focus = if item == NavItem::Recording {
|
||||
self.shell.navigation.main_mode = MainMode::Workspace;
|
||||
self.shell.navigation.selected_nav = item;
|
||||
self.shell.panels.right_focus = if item == NavItem::Recording {
|
||||
RightFocus::Recording
|
||||
} else {
|
||||
RightFocus::Default
|
||||
@@ -58,35 +70,35 @@ impl NyaTermApp {
|
||||
|
||||
match self.panel_side_for_item(item) {
|
||||
Some(PanelSide::Left) => {
|
||||
let already_open =
|
||||
self.active_left_panel == Some(item) && !self.left_sidebar_collapsed;
|
||||
let already_open = self.shell.panels.active_left == Some(item)
|
||||
&& !self.shell.panels.left_collapsed;
|
||||
if already_open {
|
||||
self.left_sidebar_collapsed = true;
|
||||
self.active_left_panel = None;
|
||||
self.shell.panels.left_collapsed = true;
|
||||
self.shell.panels.active_left = None;
|
||||
self.terminal.view.status = format!("{} closed", item.label());
|
||||
} else {
|
||||
self.active_left_panel = Some(item);
|
||||
self.left_sidebar_collapsed = false;
|
||||
self.shell.panels.active_left = Some(item);
|
||||
self.shell.panels.left_collapsed = false;
|
||||
self.terminal.view.status = format!("{} opened", item.label());
|
||||
}
|
||||
}
|
||||
Some(PanelSide::Right) => {
|
||||
let already_open =
|
||||
self.active_right_panel == Some(item) && !self.right_inspector_collapsed;
|
||||
let already_open = self.shell.panels.active_right == Some(item)
|
||||
&& !self.shell.panels.right_collapsed;
|
||||
if already_open {
|
||||
self.right_inspector_collapsed = true;
|
||||
self.active_right_panel = None;
|
||||
self.right_focus = RightFocus::Default;
|
||||
self.shell.panels.right_collapsed = true;
|
||||
self.shell.panels.active_right = None;
|
||||
self.shell.panels.right_focus = RightFocus::Default;
|
||||
self.terminal.view.status = format!("{} closed", item.label());
|
||||
} else {
|
||||
self.active_right_panel = Some(item);
|
||||
self.right_inspector_collapsed = false;
|
||||
self.shell.panels.active_right = Some(item);
|
||||
self.shell.panels.right_collapsed = false;
|
||||
self.terminal.view.status = format!("{} opened", item.label());
|
||||
}
|
||||
}
|
||||
None => {
|
||||
self.left_sidebar_collapsed = false;
|
||||
self.right_inspector_collapsed = false;
|
||||
self.shell.panels.left_collapsed = false;
|
||||
self.shell.panels.right_collapsed = false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -95,21 +107,21 @@ impl NyaTermApp {
|
||||
}
|
||||
|
||||
pub(in crate::features) fn ensure_panel_open(&mut self, item: NavItem) {
|
||||
if self.panel_multi_open && self.panel_side_for_item(item).is_some() {
|
||||
if self.shell.panels.multi_open && self.panel_side_for_item(item).is_some() {
|
||||
self.ensure_panel_in_stack(item);
|
||||
return;
|
||||
}
|
||||
self.main_mode = MainMode::Workspace;
|
||||
self.selected_nav = item;
|
||||
self.shell.navigation.main_mode = MainMode::Workspace;
|
||||
self.shell.navigation.selected_nav = item;
|
||||
match self.panel_side_for_item(item) {
|
||||
Some(PanelSide::Left) => {
|
||||
self.active_left_panel = Some(item);
|
||||
self.left_sidebar_collapsed = false;
|
||||
self.shell.panels.active_left = Some(item);
|
||||
self.shell.panels.left_collapsed = false;
|
||||
}
|
||||
Some(PanelSide::Right) => {
|
||||
self.active_right_panel = Some(item);
|
||||
self.right_inspector_collapsed = false;
|
||||
self.right_focus = if item == NavItem::Recording {
|
||||
self.shell.panels.active_right = Some(item);
|
||||
self.shell.panels.right_collapsed = false;
|
||||
self.shell.panels.right_focus = if item == NavItem::Recording {
|
||||
RightFocus::Recording
|
||||
} else {
|
||||
RightFocus::Default
|
||||
@@ -124,34 +136,40 @@ impl NyaTermApp {
|
||||
}
|
||||
|
||||
pub(in crate::features) fn toggle_left_sidebar(&mut self, cx: &mut Context<Self>) {
|
||||
if self.panel_multi_open {
|
||||
if self.shell.panels.multi_open {
|
||||
if self.left_side_open() {
|
||||
self.left_open_panels.clear();
|
||||
self.active_left_panel = None;
|
||||
self.left_sidebar_collapsed = true;
|
||||
self.shell.panels.left_open.clear();
|
||||
self.shell.panels.active_left = None;
|
||||
self.shell.panels.left_collapsed = true;
|
||||
self.terminal.view.status = "left sidebar collapsed".to_string();
|
||||
} else if let Some(panel) = self
|
||||
.shell
|
||||
.chrome
|
||||
.activity_bar_layout
|
||||
.first_panel_on_side(PanelSide::Left)
|
||||
{
|
||||
self.active_left_panel = Some(panel);
|
||||
self.left_open_panels.clear();
|
||||
self.shell.panels.active_left = Some(panel);
|
||||
self.shell.panels.left_open.clear();
|
||||
if Self::is_stackable_panel_id(panel.persistence_id()) {
|
||||
self.left_open_panels
|
||||
self.shell
|
||||
.panels
|
||||
.left_open
|
||||
.push(panel.persistence_id().to_string());
|
||||
}
|
||||
self.left_sidebar_collapsed = false;
|
||||
self.shell.panels.left_collapsed = false;
|
||||
self.terminal.view.status = "left sidebar expanded".to_string();
|
||||
}
|
||||
} else if self.left_sidebar_collapsed || self.active_left_panel.is_none() {
|
||||
self.active_left_panel = self
|
||||
} else if self.shell.panels.left_collapsed || self.shell.panels.active_left.is_none() {
|
||||
self.shell.panels.active_left = self
|
||||
.shell
|
||||
.chrome
|
||||
.activity_bar_layout
|
||||
.first_panel_on_side(PanelSide::Left);
|
||||
self.left_sidebar_collapsed = false;
|
||||
self.shell.panels.left_collapsed = false;
|
||||
self.terminal.view.status = "left sidebar expanded".to_string();
|
||||
} else {
|
||||
self.active_left_panel = None;
|
||||
self.left_sidebar_collapsed = true;
|
||||
self.shell.panels.active_left = None;
|
||||
self.shell.panels.left_collapsed = true;
|
||||
self.terminal.view.status = "left sidebar collapsed".to_string();
|
||||
}
|
||||
self.persist_ui_layout();
|
||||
@@ -159,34 +177,40 @@ impl NyaTermApp {
|
||||
}
|
||||
|
||||
pub(in crate::features) fn toggle_right_inspector(&mut self, cx: &mut Context<Self>) {
|
||||
if self.panel_multi_open {
|
||||
if self.shell.panels.multi_open {
|
||||
if self.right_side_open() {
|
||||
self.right_open_panels.clear();
|
||||
self.active_right_panel = None;
|
||||
self.right_inspector_collapsed = true;
|
||||
self.shell.panels.right_open.clear();
|
||||
self.shell.panels.active_right = None;
|
||||
self.shell.panels.right_collapsed = true;
|
||||
self.terminal.view.status = "right sidebar collapsed".to_string();
|
||||
} else if let Some(panel) = self
|
||||
.shell
|
||||
.chrome
|
||||
.activity_bar_layout
|
||||
.first_panel_on_side(PanelSide::Right)
|
||||
{
|
||||
self.active_right_panel = Some(panel);
|
||||
self.right_open_panels.clear();
|
||||
self.shell.panels.active_right = Some(panel);
|
||||
self.shell.panels.right_open.clear();
|
||||
if Self::is_stackable_panel_id(panel.persistence_id()) {
|
||||
self.right_open_panels
|
||||
self.shell
|
||||
.panels
|
||||
.right_open
|
||||
.push(panel.persistence_id().to_string());
|
||||
}
|
||||
self.right_inspector_collapsed = false;
|
||||
self.shell.panels.right_collapsed = false;
|
||||
self.terminal.view.status = "right sidebar expanded".to_string();
|
||||
}
|
||||
} else if self.right_inspector_collapsed || self.active_right_panel.is_none() {
|
||||
self.active_right_panel = self
|
||||
} else if self.shell.panels.right_collapsed || self.shell.panels.active_right.is_none() {
|
||||
self.shell.panels.active_right = self
|
||||
.shell
|
||||
.chrome
|
||||
.activity_bar_layout
|
||||
.first_panel_on_side(PanelSide::Right);
|
||||
self.right_inspector_collapsed = false;
|
||||
self.shell.panels.right_collapsed = false;
|
||||
self.terminal.view.status = "right sidebar expanded".to_string();
|
||||
} else {
|
||||
self.active_right_panel = None;
|
||||
self.right_inspector_collapsed = true;
|
||||
self.shell.panels.active_right = None;
|
||||
self.shell.panels.right_collapsed = true;
|
||||
self.terminal.view.status = "right sidebar collapsed".to_string();
|
||||
}
|
||||
self.persist_ui_layout();
|
||||
@@ -194,46 +218,50 @@ impl NyaTermApp {
|
||||
}
|
||||
|
||||
pub(in crate::features) fn toggle_mobile_left_drawer(&mut self, cx: &mut Context<Self>) {
|
||||
if self.mobile_left_open {
|
||||
self.mobile_left_open = false;
|
||||
if self.shell.panels.mobile_left_open {
|
||||
self.shell.panels.mobile_left_open = false;
|
||||
} else {
|
||||
if self.active_left_panel.is_none() {
|
||||
self.active_left_panel = self
|
||||
if self.shell.panels.active_left.is_none() {
|
||||
self.shell.panels.active_left = self
|
||||
.shell
|
||||
.chrome
|
||||
.activity_bar_layout
|
||||
.first_panel_on_side(PanelSide::Left);
|
||||
}
|
||||
self.left_sidebar_collapsed = false;
|
||||
self.mobile_left_open = true;
|
||||
self.shell.panels.left_collapsed = false;
|
||||
self.shell.panels.mobile_left_open = true;
|
||||
}
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
pub(in crate::features) fn toggle_mobile_right_drawer(&mut self, cx: &mut Context<Self>) {
|
||||
if self.mobile_right_open {
|
||||
self.mobile_right_open = false;
|
||||
if self.shell.panels.mobile_right_open {
|
||||
self.shell.panels.mobile_right_open = false;
|
||||
} else {
|
||||
if self.active_right_panel.is_none() {
|
||||
self.active_right_panel = self
|
||||
if self.shell.panels.active_right.is_none() {
|
||||
self.shell.panels.active_right = self
|
||||
.shell
|
||||
.chrome
|
||||
.activity_bar_layout
|
||||
.first_panel_on_side(PanelSide::Right);
|
||||
}
|
||||
self.right_inspector_collapsed = false;
|
||||
self.mobile_right_open = true;
|
||||
self.shell.panels.right_collapsed = false;
|
||||
self.shell.panels.mobile_right_open = true;
|
||||
}
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
pub(in crate::features) fn current_left_panel(&self) -> Option<NavItem> {
|
||||
if self.left_sidebar_collapsed {
|
||||
if self.shell.panels.left_collapsed {
|
||||
return None;
|
||||
}
|
||||
if self.panel_multi_open {
|
||||
if self.shell.panels.multi_open {
|
||||
if self.side_overlay_panel(PanelSide::Left).is_some()
|
||||
|| !self.side_open_panel_ids(PanelSide::Left).is_empty()
|
||||
{
|
||||
return self
|
||||
.side_overlay_panel(PanelSide::Left)
|
||||
.or(self.active_left_panel)
|
||||
.or(self.shell.panels.active_left)
|
||||
.or_else(|| {
|
||||
self.side_open_panel_ids(PanelSide::Left)
|
||||
.first()
|
||||
@@ -242,52 +270,55 @@ impl NyaTermApp {
|
||||
}
|
||||
return None;
|
||||
}
|
||||
self.active_left_panel
|
||||
self.shell.panels.active_left
|
||||
}
|
||||
|
||||
pub(in crate::features) fn current_right_panel(&self) -> Option<NavItem> {
|
||||
if self.right_inspector_collapsed {
|
||||
if self.shell.panels.right_collapsed {
|
||||
return None;
|
||||
}
|
||||
if self.panel_multi_open {
|
||||
if self.shell.panels.multi_open {
|
||||
if self.side_overlay_panel(PanelSide::Right).is_some()
|
||||
|| !self.side_open_panel_ids(PanelSide::Right).is_empty()
|
||||
{
|
||||
return self
|
||||
.side_overlay_panel(PanelSide::Right)
|
||||
.or(self.active_right_panel)
|
||||
.or(self.shell.panels.active_right)
|
||||
.or_else(|| {
|
||||
self.side_open_panel_ids(PanelSide::Right)
|
||||
.first()
|
||||
.and_then(|id| NavItem::from_persistence_id(id))
|
||||
})
|
||||
.or(if self.right_focus == RightFocus::Recording {
|
||||
.or(if self.shell.panels.right_focus == RightFocus::Recording {
|
||||
Some(NavItem::Recording)
|
||||
} else {
|
||||
None
|
||||
});
|
||||
}
|
||||
return if self.right_focus == RightFocus::Recording {
|
||||
return if self.shell.panels.right_focus == RightFocus::Recording {
|
||||
Some(NavItem::Recording)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
}
|
||||
self.active_right_panel
|
||||
.or(if self.right_focus == RightFocus::Recording {
|
||||
self.shell.panels.active_right.or(
|
||||
if self.shell.panels.right_focus == RightFocus::Recording {
|
||||
Some(NavItem::Recording)
|
||||
} else {
|
||||
None
|
||||
})
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
pub(in crate::features) fn left_side_open(&self) -> bool {
|
||||
self.current_left_panel().is_some()
|
||||
|| (self.panel_multi_open && !self.side_open_panel_ids(PanelSide::Left).is_empty())
|
||||
|| (self.shell.panels.multi_open
|
||||
&& !self.side_open_panel_ids(PanelSide::Left).is_empty())
|
||||
}
|
||||
|
||||
pub(in crate::features) fn right_side_open(&self) -> bool {
|
||||
self.current_right_panel().is_some()
|
||||
|| (self.panel_multi_open && !self.side_open_panel_ids(PanelSide::Right).is_empty())
|
||||
|| (self.shell.panels.multi_open
|
||||
&& !self.side_open_panel_ids(PanelSide::Right).is_empty())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,14 +6,10 @@ use nyaterm_core::ConnectionStore;
|
||||
|
||||
use crate::features::NyaTermApp;
|
||||
use crate::models::{
|
||||
BottomPanelMode, NavItem, PanelResizeSide, PanelResizeState, PanelSide,
|
||||
TransferHeightResizeState, panel_collapsed_from_persistence,
|
||||
BottomPanelMode, NavItem, PanelResizeSide, PanelSide, TransferHeightResizeState,
|
||||
panel_collapsed_from_persistence,
|
||||
};
|
||||
|
||||
const LEFT_PANEL_MIN: f32 = 160.;
|
||||
const LEFT_PANEL_MAX: f32 = 720.;
|
||||
const RIGHT_PANEL_MIN: f32 = 200.;
|
||||
const RIGHT_PANEL_MAX: f32 = 720.;
|
||||
const QUICK_CMD_HEIGHT_MIN: f32 = 36.;
|
||||
const SERIAL_SEND_HEIGHT_MIN: f32 = 60.;
|
||||
const BOTTOM_PANEL_HEIGHT_MAX: f32 = 520.;
|
||||
@@ -32,15 +28,7 @@ impl NyaTermApp {
|
||||
event: &MouseDownEvent,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
let start_width = match side {
|
||||
PanelResizeSide::Left => px(self.left_panel_width),
|
||||
PanelResizeSide::Right => px(self.right_panel_width),
|
||||
};
|
||||
self.panel_resize = Some(PanelResizeState {
|
||||
side,
|
||||
start_x: event.position.x,
|
||||
start_width,
|
||||
});
|
||||
self.shell.panels.start_resize(side, event.position.x);
|
||||
self.terminal.view.status = match side {
|
||||
PanelResizeSide::Left => "resizing left panel".to_string(),
|
||||
PanelResizeSide::Right => "resizing right panel".to_string(),
|
||||
@@ -53,22 +41,16 @@ impl NyaTermApp {
|
||||
event: &MouseMoveEvent,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
let Some(state) = self.panel_resize else {
|
||||
let Some((side, width)) = self.shell.panels.update_resize(event.position.x) else {
|
||||
return;
|
||||
};
|
||||
let delta = f32::from(event.position.x - state.start_x);
|
||||
let start = f32::from(state.start_width);
|
||||
match state.side {
|
||||
match side {
|
||||
PanelResizeSide::Left => {
|
||||
self.left_panel_width = (start + delta).clamp(LEFT_PANEL_MIN, LEFT_PANEL_MAX);
|
||||
self.terminal.view.status =
|
||||
format!("left panel: {:.0}px", self.left_panel_width.round());
|
||||
self.terminal.view.status = format!("left panel: {:.0}px", width.round());
|
||||
}
|
||||
PanelResizeSide::Right => {
|
||||
// Right handle sits on the left edge of the right panel: drag left grows width.
|
||||
self.right_panel_width = (start - delta).clamp(RIGHT_PANEL_MIN, RIGHT_PANEL_MAX);
|
||||
self.terminal.view.status =
|
||||
format!("right panel: {:.0}px", self.right_panel_width.round());
|
||||
self.terminal.view.status = format!("right panel: {:.0}px", width.round());
|
||||
}
|
||||
}
|
||||
cx.notify();
|
||||
@@ -79,12 +61,12 @@ impl NyaTermApp {
|
||||
_event: &MouseUpEvent,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
if self.panel_resize.take().is_some() {
|
||||
if self.shell.panels.finish_resize() {
|
||||
self.persist_panel_widths();
|
||||
self.terminal.view.status = format!(
|
||||
"panel sizes L{:.0}/R{:.0}",
|
||||
self.left_panel_width.round(),
|
||||
self.right_panel_width.round()
|
||||
self.shell.panels.left_width.round(),
|
||||
self.shell.panels.right_width.round()
|
||||
);
|
||||
cx.notify();
|
||||
}
|
||||
@@ -95,34 +77,34 @@ impl NyaTermApp {
|
||||
}
|
||||
|
||||
pub(in crate::features) fn apply_ui_layout_from_settings(&mut self) {
|
||||
self.left_panel_width = self.settings.ui_left_panel_width as f32;
|
||||
self.right_panel_width = self.settings.ui_right_panel_width as f32;
|
||||
self.shell.panels.left_width = self.settings.ui_left_panel_width as f32;
|
||||
self.shell.panels.right_width = self.settings.ui_right_panel_width as f32;
|
||||
self.transfer.panel.height = self.settings.ui_transfer_height as f32;
|
||||
self.shell.bottom_panel.quick_commands_height = self.settings.ui_quick_cmd_height as f32;
|
||||
self.shell.bottom_panel.command_send_height = self.settings.ui_serial_send_height as f32;
|
||||
self.apply_activity_layout_from_settings();
|
||||
self.active_left_panel = self
|
||||
self.shell.panels.active_left = self
|
||||
.settings
|
||||
.ui_active_left_panel
|
||||
.as_deref()
|
||||
.and_then(NavItem::from_persistence_id)
|
||||
.filter(|item| self.panel_side_for_item(*item) == Some(PanelSide::Left));
|
||||
self.active_right_panel = self
|
||||
self.shell.panels.active_right = self
|
||||
.settings
|
||||
.ui_active_right_panel
|
||||
.as_deref()
|
||||
.and_then(NavItem::from_persistence_id)
|
||||
.filter(|item| self.panel_side_for_item(*item) == Some(PanelSide::Right));
|
||||
self.left_sidebar_collapsed = panel_collapsed_from_persistence(
|
||||
self.shell.panels.left_collapsed = panel_collapsed_from_persistence(
|
||||
self.settings.ui_left_panel_collapsed,
|
||||
self.settings.ui_panel_multi_open,
|
||||
self.active_left_panel.is_some(),
|
||||
self.shell.panels.active_left.is_some(),
|
||||
!self.settings.ui_left_open_panels.is_empty(),
|
||||
);
|
||||
self.right_inspector_collapsed = panel_collapsed_from_persistence(
|
||||
self.shell.panels.right_collapsed = panel_collapsed_from_persistence(
|
||||
self.settings.ui_right_panel_collapsed,
|
||||
self.settings.ui_panel_multi_open,
|
||||
self.active_right_panel.is_some(),
|
||||
self.shell.panels.active_right.is_some(),
|
||||
!self.settings.ui_right_open_panels.is_empty(),
|
||||
);
|
||||
self.apply_panel_stack_from_settings();
|
||||
@@ -132,9 +114,10 @@ impl NyaTermApp {
|
||||
}
|
||||
|
||||
pub(in crate::features) fn persist_ui_layout(&mut self) {
|
||||
self.settings.ui_left_panel_width = self.left_panel_width.round().clamp(160., 720.) as u32;
|
||||
self.settings.ui_left_panel_width =
|
||||
self.shell.panels.left_width.round().clamp(160., 720.) as u32;
|
||||
self.settings.ui_right_panel_width =
|
||||
self.right_panel_width.round().clamp(200., 720.) as u32;
|
||||
self.shell.panels.right_width.round().clamp(200., 720.) as u32;
|
||||
self.settings.ui_transfer_height =
|
||||
self.transfer.panel.height.round().clamp(60., 600.) as u32;
|
||||
self.settings.ui_quick_cmd_height =
|
||||
@@ -150,13 +133,17 @@ impl NyaTermApp {
|
||||
.round()
|
||||
.clamp(SERIAL_SEND_HEIGHT_MIN, BOTTOM_PANEL_HEIGHT_MAX) as u32;
|
||||
self.settings.ui_active_left_panel = self
|
||||
.active_left_panel
|
||||
.shell
|
||||
.panels
|
||||
.active_left
|
||||
.map(|item| item.persistence_id().to_string());
|
||||
self.settings.ui_active_right_panel = self
|
||||
.active_right_panel
|
||||
.shell
|
||||
.panels
|
||||
.active_right
|
||||
.map(|item| item.persistence_id().to_string());
|
||||
self.settings.ui_left_panel_collapsed = self.left_sidebar_collapsed;
|
||||
self.settings.ui_right_panel_collapsed = self.right_inspector_collapsed;
|
||||
self.settings.ui_left_panel_collapsed = self.shell.panels.left_collapsed;
|
||||
self.settings.ui_right_panel_collapsed = self.shell.panels.right_collapsed;
|
||||
self.settings.ui_saved_connections_sort_mode = self
|
||||
.connection_state
|
||||
.list_sort_mode()
|
||||
|
||||
@@ -10,8 +10,8 @@ use nyaterm_core::{AgentCommandExecutionMode, truncate_preview};
|
||||
|
||||
use crate::features::{ChromeTooltip, NyaTermApp, TextInputSetup, panel_header_with_actions};
|
||||
use crate::models::{
|
||||
ActivityBarZone, MainMode, NavItem, NetworkTab, PanelSide, PanelStackResizeState, RightFocus,
|
||||
SecurityAuthTab, SettingsTab,
|
||||
ActivityBarZone, MainMode, NavItem, NetworkTab, PanelSide, RightFocus, SecurityAuthTab,
|
||||
SettingsTab,
|
||||
};
|
||||
use crate::theme::ThemePalette;
|
||||
|
||||
@@ -28,41 +28,45 @@ impl NyaTermApp {
|
||||
}
|
||||
|
||||
pub(in crate::features) fn toggle_panel_multi_open(&mut self, cx: &mut Context<Self>) {
|
||||
self.panel_multi_open = !self.panel_multi_open;
|
||||
if self.panel_multi_open {
|
||||
if self.left_open_panels.is_empty() {
|
||||
if let Some(panel) = self.active_left_panel {
|
||||
self.shell.panels.multi_open = !self.shell.panels.multi_open;
|
||||
if self.shell.panels.multi_open {
|
||||
if self.shell.panels.left_open.is_empty() {
|
||||
if let Some(panel) = self.shell.panels.active_left {
|
||||
let id = panel.persistence_id().to_string();
|
||||
if Self::is_stackable_panel_id(&id) {
|
||||
self.left_open_panels.push(id);
|
||||
self.shell.panels.left_open.push(id);
|
||||
}
|
||||
}
|
||||
}
|
||||
if self.right_open_panels.is_empty() {
|
||||
if let Some(panel) = self.active_right_panel {
|
||||
if self.shell.panels.right_open.is_empty() {
|
||||
if let Some(panel) = self.shell.panels.active_right {
|
||||
let id = panel.persistence_id().to_string();
|
||||
if Self::is_stackable_panel_id(&id) {
|
||||
self.right_open_panels.push(id);
|
||||
self.shell.panels.right_open.push(id);
|
||||
}
|
||||
}
|
||||
}
|
||||
self.terminal.view.status = "multi-open panels enabled".to_string();
|
||||
} else {
|
||||
// Collapse to active-only mode.
|
||||
if self.active_left_panel.is_none() {
|
||||
self.active_left_panel = self
|
||||
.left_open_panels
|
||||
if self.shell.panels.active_left.is_none() {
|
||||
self.shell.panels.active_left = self
|
||||
.shell
|
||||
.panels
|
||||
.left_open
|
||||
.first()
|
||||
.and_then(|id| NavItem::from_persistence_id(id));
|
||||
}
|
||||
if self.active_right_panel.is_none() {
|
||||
self.active_right_panel = self
|
||||
.right_open_panels
|
||||
if self.shell.panels.active_right.is_none() {
|
||||
self.shell.panels.active_right = self
|
||||
.shell
|
||||
.panels
|
||||
.right_open
|
||||
.first()
|
||||
.and_then(|id| NavItem::from_persistence_id(id));
|
||||
}
|
||||
self.left_open_panels.clear();
|
||||
self.right_open_panels.clear();
|
||||
self.shell.panels.left_open.clear();
|
||||
self.shell.panels.right_open.clear();
|
||||
self.terminal.view.status = "single panel mode".to_string();
|
||||
}
|
||||
self.persist_ui_layout();
|
||||
@@ -70,10 +74,10 @@ impl NyaTermApp {
|
||||
}
|
||||
|
||||
pub(in crate::features) fn side_open_panel_ids(&self, side: PanelSide) -> Vec<String> {
|
||||
if !self.panel_multi_open {
|
||||
if !self.shell.panels.multi_open {
|
||||
let active = match side {
|
||||
PanelSide::Left => self.active_left_panel,
|
||||
PanelSide::Right => self.active_right_panel,
|
||||
PanelSide::Left => self.shell.panels.active_left,
|
||||
PanelSide::Right => self.shell.panels.active_right,
|
||||
};
|
||||
return active
|
||||
.map(|item| item.persistence_id().to_string())
|
||||
@@ -82,8 +86,8 @@ impl NyaTermApp {
|
||||
}
|
||||
|
||||
let open = match side {
|
||||
PanelSide::Left => &self.left_open_panels,
|
||||
PanelSide::Right => &self.right_open_panels,
|
||||
PanelSide::Left => &self.shell.panels.left_open,
|
||||
PanelSide::Right => &self.shell.panels.right_open,
|
||||
};
|
||||
if open.is_empty() {
|
||||
return Vec::new();
|
||||
@@ -95,7 +99,7 @@ impl NyaTermApp {
|
||||
};
|
||||
let mut ordered = Vec::new();
|
||||
for zone in zones {
|
||||
for id in self.activity_bar_layout.zone(zone) {
|
||||
for id in self.shell.chrome.activity_bar_layout.zone(zone) {
|
||||
if open_set.contains(id) && Self::is_stackable_panel_id(id) {
|
||||
ordered.push(id.clone());
|
||||
}
|
||||
@@ -105,27 +109,25 @@ impl NyaTermApp {
|
||||
}
|
||||
|
||||
pub(in crate::features) fn side_overlay_panel(&self, side: PanelSide) -> Option<NavItem> {
|
||||
if !self.panel_multi_open {
|
||||
if !self.shell.panels.multi_open {
|
||||
return None;
|
||||
}
|
||||
let active = match side {
|
||||
PanelSide::Left => self.active_left_panel,
|
||||
PanelSide::Right => self.active_right_panel,
|
||||
PanelSide::Left => self.shell.panels.active_left,
|
||||
PanelSide::Right => self.shell.panels.active_right,
|
||||
}?;
|
||||
let id = active.persistence_id();
|
||||
Self::is_exclusive_panel_id(id).then_some(active)
|
||||
}
|
||||
|
||||
pub(in crate::features) fn panel_stack_weight(&self, panel_id: &str) -> f32 {
|
||||
self.panel_stack_sizes
|
||||
.get(panel_id)
|
||||
.copied()
|
||||
.filter(|value| value.is_finite() && *value > 0.)
|
||||
.unwrap_or(1.)
|
||||
self.shell.panels.stack_weight(panel_id)
|
||||
}
|
||||
|
||||
pub(in crate::features) fn panel_side_for_item(&self, item: NavItem) -> Option<PanelSide> {
|
||||
self.activity_bar_layout
|
||||
self.shell
|
||||
.chrome
|
||||
.activity_bar_layout
|
||||
.side_for_entry(item.persistence_id())
|
||||
.or_else(|| item.is_left_panel().then_some(PanelSide::Left))
|
||||
.or_else(|| item.is_right_panel().then_some(PanelSide::Right))
|
||||
@@ -140,7 +142,7 @@ impl NyaTermApp {
|
||||
self.open_page(NavItem::Settings, cx);
|
||||
return;
|
||||
}
|
||||
if !self.panel_multi_open {
|
||||
if !self.shell.panels.multi_open {
|
||||
self.open_panel(item, cx);
|
||||
return;
|
||||
}
|
||||
@@ -151,18 +153,18 @@ impl NyaTermApp {
|
||||
return;
|
||||
};
|
||||
|
||||
self.main_mode = MainMode::Workspace;
|
||||
self.selected_nav = item;
|
||||
self.shell.navigation.main_mode = MainMode::Workspace;
|
||||
self.shell.navigation.selected_nav = item;
|
||||
if item == NavItem::Recording {
|
||||
self.right_focus = RightFocus::Recording;
|
||||
self.shell.panels.right_focus = RightFocus::Recording;
|
||||
} else {
|
||||
self.right_focus = RightFocus::Default;
|
||||
self.shell.panels.right_focus = RightFocus::Default;
|
||||
}
|
||||
|
||||
if Self::is_exclusive_panel_id(&id) {
|
||||
let active = match side {
|
||||
PanelSide::Left => self.active_left_panel,
|
||||
PanelSide::Right => self.active_right_panel,
|
||||
PanelSide::Left => self.shell.panels.active_left,
|
||||
PanelSide::Right => self.shell.panels.active_right,
|
||||
};
|
||||
if active == Some(item) {
|
||||
// Dismiss exclusive overlay to stack.
|
||||
@@ -172,24 +174,24 @@ impl NyaTermApp {
|
||||
.find_map(|open_id| NavItem::from_persistence_id(&open_id));
|
||||
match side {
|
||||
PanelSide::Left => {
|
||||
self.active_left_panel = fallback;
|
||||
self.left_sidebar_collapsed = fallback.is_none();
|
||||
self.shell.panels.active_left = fallback;
|
||||
self.shell.panels.left_collapsed = fallback.is_none();
|
||||
}
|
||||
PanelSide::Right => {
|
||||
self.active_right_panel = fallback;
|
||||
self.right_inspector_collapsed = fallback.is_none();
|
||||
self.shell.panels.active_right = fallback;
|
||||
self.shell.panels.right_collapsed = fallback.is_none();
|
||||
}
|
||||
}
|
||||
self.terminal.view.status = format!("{} closed", item.label());
|
||||
} else {
|
||||
match side {
|
||||
PanelSide::Left => {
|
||||
self.active_left_panel = Some(item);
|
||||
self.left_sidebar_collapsed = false;
|
||||
self.shell.panels.active_left = Some(item);
|
||||
self.shell.panels.left_collapsed = false;
|
||||
}
|
||||
PanelSide::Right => {
|
||||
self.active_right_panel = Some(item);
|
||||
self.right_inspector_collapsed = false;
|
||||
self.shell.panels.active_right = Some(item);
|
||||
self.shell.panels.right_collapsed = false;
|
||||
}
|
||||
}
|
||||
self.terminal.view.status = format!("{} opened", item.label());
|
||||
@@ -200,13 +202,13 @@ impl NyaTermApp {
|
||||
}
|
||||
|
||||
let open_list = match side {
|
||||
PanelSide::Left => &mut self.left_open_panels,
|
||||
PanelSide::Right => &mut self.right_open_panels,
|
||||
PanelSide::Left => &mut self.shell.panels.left_open,
|
||||
PanelSide::Right => &mut self.shell.panels.right_open,
|
||||
};
|
||||
let is_open = open_list.iter().any(|value| value == &id);
|
||||
let active = match side {
|
||||
PanelSide::Left => self.active_left_panel,
|
||||
PanelSide::Right => self.active_right_panel,
|
||||
PanelSide::Left => self.shell.panels.active_left,
|
||||
PanelSide::Right => self.shell.panels.active_right,
|
||||
};
|
||||
|
||||
// If exclusive overlay is showing and stacked panel already open, reveal stack.
|
||||
@@ -217,12 +219,12 @@ impl NyaTermApp {
|
||||
{
|
||||
match side {
|
||||
PanelSide::Left => {
|
||||
self.active_left_panel = Some(item);
|
||||
self.left_sidebar_collapsed = false;
|
||||
self.shell.panels.active_left = Some(item);
|
||||
self.shell.panels.left_collapsed = false;
|
||||
}
|
||||
PanelSide::Right => {
|
||||
self.active_right_panel = Some(item);
|
||||
self.right_inspector_collapsed = false;
|
||||
self.shell.panels.active_right = Some(item);
|
||||
self.shell.panels.right_collapsed = false;
|
||||
}
|
||||
}
|
||||
self.terminal.view.status = format!("{} focused", item.label());
|
||||
@@ -247,14 +249,14 @@ impl NyaTermApp {
|
||||
};
|
||||
match side {
|
||||
PanelSide::Left => {
|
||||
self.active_left_panel = next_active;
|
||||
self.left_sidebar_collapsed =
|
||||
next_active.is_none() && self.left_open_panels.is_empty();
|
||||
self.shell.panels.active_left = next_active;
|
||||
self.shell.panels.left_collapsed =
|
||||
next_active.is_none() && self.shell.panels.left_open.is_empty();
|
||||
}
|
||||
PanelSide::Right => {
|
||||
self.active_right_panel = next_active;
|
||||
self.right_inspector_collapsed =
|
||||
next_active.is_none() && self.right_open_panels.is_empty();
|
||||
self.shell.panels.active_right = next_active;
|
||||
self.shell.panels.right_collapsed =
|
||||
next_active.is_none() && self.shell.panels.right_open.is_empty();
|
||||
}
|
||||
}
|
||||
self.terminal.view.status = format!("{} closed", item.label());
|
||||
@@ -262,12 +264,12 @@ impl NyaTermApp {
|
||||
open_list.push(id);
|
||||
match side {
|
||||
PanelSide::Left => {
|
||||
self.active_left_panel = Some(item);
|
||||
self.left_sidebar_collapsed = false;
|
||||
self.shell.panels.active_left = Some(item);
|
||||
self.shell.panels.left_collapsed = false;
|
||||
}
|
||||
PanelSide::Right => {
|
||||
self.active_right_panel = Some(item);
|
||||
self.right_inspector_collapsed = false;
|
||||
self.shell.panels.active_right = Some(item);
|
||||
self.shell.panels.right_collapsed = false;
|
||||
}
|
||||
}
|
||||
self.terminal.view.status = format!("{} opened", item.label());
|
||||
@@ -277,39 +279,45 @@ impl NyaTermApp {
|
||||
}
|
||||
|
||||
pub(in crate::features) fn ensure_panel_in_stack(&mut self, item: NavItem) {
|
||||
self.main_mode = MainMode::Workspace;
|
||||
self.selected_nav = item;
|
||||
if !self.panel_multi_open {
|
||||
self.shell.navigation.main_mode = MainMode::Workspace;
|
||||
self.shell.navigation.selected_nav = item;
|
||||
if !self.shell.panels.multi_open {
|
||||
self.ensure_panel_open(item);
|
||||
return;
|
||||
}
|
||||
let id = item.persistence_id().to_string();
|
||||
match self.panel_side_for_item(item) {
|
||||
Some(PanelSide::Left) => {
|
||||
self.left_sidebar_collapsed = false;
|
||||
self.shell.panels.left_collapsed = false;
|
||||
if Self::is_exclusive_panel_id(&id) {
|
||||
self.active_left_panel = Some(item);
|
||||
self.shell.panels.active_left = Some(item);
|
||||
} else {
|
||||
if !self.left_open_panels.iter().any(|value| value == &id) {
|
||||
self.left_open_panels.push(id);
|
||||
if !self.shell.panels.left_open.iter().any(|value| value == &id) {
|
||||
self.shell.panels.left_open.push(id);
|
||||
}
|
||||
self.active_left_panel = Some(item);
|
||||
self.shell.panels.active_left = Some(item);
|
||||
}
|
||||
}
|
||||
Some(PanelSide::Right) => {
|
||||
self.right_inspector_collapsed = false;
|
||||
self.right_focus = if item == NavItem::Recording {
|
||||
self.shell.panels.right_collapsed = false;
|
||||
self.shell.panels.right_focus = if item == NavItem::Recording {
|
||||
RightFocus::Recording
|
||||
} else {
|
||||
RightFocus::Default
|
||||
};
|
||||
if Self::is_exclusive_panel_id(&id) {
|
||||
self.active_right_panel = Some(item);
|
||||
self.shell.panels.active_right = Some(item);
|
||||
} else {
|
||||
if !self.right_open_panels.iter().any(|value| value == &id) {
|
||||
self.right_open_panels.push(id);
|
||||
if !self
|
||||
.shell
|
||||
.panels
|
||||
.right_open
|
||||
.iter()
|
||||
.any(|value| value == &id)
|
||||
{
|
||||
self.shell.panels.right_open.push(id);
|
||||
}
|
||||
self.active_right_panel = Some(item);
|
||||
self.shell.panels.active_right = Some(item);
|
||||
}
|
||||
}
|
||||
None => {}
|
||||
@@ -325,15 +333,13 @@ impl NyaTermApp {
|
||||
container_height: f32,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
self.panel_stack_resize = Some(PanelStackResizeState {
|
||||
self.shell.panels.start_stack_resize(
|
||||
side,
|
||||
above_id: above_id.clone(),
|
||||
below_id: below_id.clone(),
|
||||
start_y: event.position.y,
|
||||
above_weight: self.panel_stack_weight(&above_id),
|
||||
below_weight: self.panel_stack_weight(&below_id),
|
||||
container_height: container_height.max(1.),
|
||||
});
|
||||
above_id,
|
||||
below_id,
|
||||
event.position.y,
|
||||
container_height,
|
||||
);
|
||||
self.terminal.view.status = "resizing panel stack".to_string();
|
||||
cx.notify();
|
||||
}
|
||||
@@ -343,24 +349,9 @@ impl NyaTermApp {
|
||||
event: &MouseMoveEvent,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
let Some(state) = self.panel_stack_resize.clone() else {
|
||||
return;
|
||||
};
|
||||
let delta_px = f32::from(event.position.y - state.start_y);
|
||||
let pair = state.above_weight + state.below_weight;
|
||||
if pair <= 0. || state.container_height <= 0. {
|
||||
return;
|
||||
if self.shell.panels.update_stack_resize(event.position.y) {
|
||||
cx.notify();
|
||||
}
|
||||
let px_per_weight = state.container_height / pair;
|
||||
let min_weight = (48. / px_per_weight).min(pair / 2.).max(0.05);
|
||||
let next_above =
|
||||
(state.above_weight + delta_px / px_per_weight).clamp(min_weight, pair - min_weight);
|
||||
let next_below = pair - next_above;
|
||||
self.panel_stack_sizes
|
||||
.insert(state.above_id.clone(), next_above);
|
||||
self.panel_stack_sizes
|
||||
.insert(state.below_id.clone(), next_below);
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
pub(in crate::features) fn finish_panel_stack_resize(
|
||||
@@ -368,7 +359,7 @@ impl NyaTermApp {
|
||||
_event: &MouseUpEvent,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
if self.panel_stack_resize.take().is_some() {
|
||||
if self.shell.panels.finish_stack_resize() {
|
||||
self.persist_ui_layout();
|
||||
self.terminal.view.status = "panel stack sizes saved".to_string();
|
||||
cx.notify();
|
||||
@@ -376,11 +367,13 @@ impl NyaTermApp {
|
||||
}
|
||||
|
||||
pub(in crate::features) fn sync_panel_stack_to_settings(&mut self) {
|
||||
self.settings.ui_panel_multi_open = self.panel_multi_open;
|
||||
self.settings.ui_left_open_panels = self.left_open_panels.clone();
|
||||
self.settings.ui_right_open_panels = self.right_open_panels.clone();
|
||||
self.settings.ui_panel_multi_open = self.shell.panels.multi_open;
|
||||
self.settings.ui_left_open_panels = self.shell.panels.left_open.clone();
|
||||
self.settings.ui_right_open_panels = self.shell.panels.right_open.clone();
|
||||
self.settings.ui_panel_stack_sizes = self
|
||||
.panel_stack_sizes
|
||||
.shell
|
||||
.panels
|
||||
.stack_sizes
|
||||
.iter()
|
||||
.filter_map(|(key, value)| {
|
||||
let scaled = (*value * 1000.).round();
|
||||
@@ -390,29 +383,29 @@ impl NyaTermApp {
|
||||
}
|
||||
|
||||
pub(in crate::features) fn apply_panel_stack_from_settings(&mut self) {
|
||||
self.panel_multi_open = self.settings.ui_panel_multi_open;
|
||||
self.left_open_panels = self.settings.ui_left_open_panels.clone();
|
||||
self.right_open_panels = self.settings.ui_right_open_panels.clone();
|
||||
self.panel_stack_sizes = self
|
||||
self.shell.panels.multi_open = self.settings.ui_panel_multi_open;
|
||||
self.shell.panels.left_open = self.settings.ui_left_open_panels.clone();
|
||||
self.shell.panels.right_open = self.settings.ui_right_open_panels.clone();
|
||||
self.shell.panels.stack_sizes = self
|
||||
.settings
|
||||
.ui_panel_stack_sizes
|
||||
.iter()
|
||||
.filter_map(|(key, value)| (*value > 0).then(|| (key.clone(), (*value as f32) / 1000.)))
|
||||
.collect();
|
||||
if self.panel_multi_open {
|
||||
if self.left_open_panels.is_empty() {
|
||||
if let Some(panel) = self.active_left_panel {
|
||||
if self.shell.panels.multi_open {
|
||||
if self.shell.panels.left_open.is_empty() {
|
||||
if let Some(panel) = self.shell.panels.active_left {
|
||||
let id = panel.persistence_id().to_string();
|
||||
if Self::is_stackable_panel_id(&id) {
|
||||
self.left_open_panels.push(id);
|
||||
self.shell.panels.left_open.push(id);
|
||||
}
|
||||
}
|
||||
}
|
||||
if self.right_open_panels.is_empty() {
|
||||
if let Some(panel) = self.active_right_panel {
|
||||
if self.shell.panels.right_open.is_empty() {
|
||||
if let Some(panel) = self.shell.panels.active_right {
|
||||
let id = panel.persistence_id().to_string();
|
||||
if Self::is_stackable_panel_id(&id) {
|
||||
self.right_open_panels.push(id);
|
||||
self.shell.panels.right_open.push(id);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -433,14 +426,24 @@ impl NyaTermApp {
|
||||
PanelSide::Left => self.current_left_panel(),
|
||||
PanelSide::Right => self.current_right_panel(),
|
||||
}
|
||||
.or_else(|| self.activity_bar_layout.first_panel_on_side(side))
|
||||
.or_else(|| {
|
||||
self.shell
|
||||
.chrome
|
||||
.activity_bar_layout
|
||||
.first_panel_on_side(side)
|
||||
})
|
||||
.unwrap_or(NavItem::Workspace);
|
||||
self.single_side_panel(side, fallback, window, cx)
|
||||
} else if open_ids.len() == 1 || !self.panel_multi_open {
|
||||
} else if open_ids.len() == 1 || !self.shell.panels.multi_open {
|
||||
let panel = open_ids
|
||||
.first()
|
||||
.and_then(|id| NavItem::from_persistence_id(id))
|
||||
.or_else(|| self.activity_bar_layout.first_panel_on_side(side))
|
||||
.or_else(|| {
|
||||
self.shell
|
||||
.chrome
|
||||
.activity_bar_layout
|
||||
.first_panel_on_side(side)
|
||||
})
|
||||
.unwrap_or(NavItem::Workspace);
|
||||
self.single_side_panel(side, panel, window, cx)
|
||||
} else {
|
||||
@@ -701,7 +704,7 @@ impl NyaTermApp {
|
||||
cx.listener(|this, _, _, cx| {
|
||||
this.ai.history.open = false;
|
||||
this.ai.panel.execution_menu_open = false;
|
||||
this.settings_active_tab = SettingsTab::AiGeneral;
|
||||
this.shell.navigation.settings.active_tab = SettingsTab::AiGeneral;
|
||||
this.open_page(NavItem::Settings, cx);
|
||||
}),
|
||||
))
|
||||
|
||||
@@ -4,12 +4,44 @@
|
||||
//! lifecycles that span those views so the composition root does not retain
|
||||
//! independently mutable mirrors.
|
||||
|
||||
use gpui::Pixels;
|
||||
use std::collections::{HashMap, HashSet};
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
use crate::models::{BottomPanelMode, BottomPanelResizeState};
|
||||
use gpui::{Pixels, ScrollHandle, WindowHandle};
|
||||
|
||||
use super::super::app_state::SettingsDraftSnapshot;
|
||||
use super::super::settings_window::SettingsWindow;
|
||||
use crate::models::{
|
||||
ActivityBarContextMenuState, ActivityBarLayoutState, BottomPanelMode, BottomPanelResizeState,
|
||||
HeaderStatusState, MainMode, NavItem, PanelResizeSide, PanelResizeState, PanelSide,
|
||||
PanelStackResizeState, RightFocus, SettingsTab, TitleMenu, TitleMenuSubmenu, WorkspacePaneNode,
|
||||
WorkspaceSplitResizeState, WorkspaceSplitState,
|
||||
};
|
||||
|
||||
pub(in crate::features) struct ShellFeatureState {
|
||||
pub bottom_panel: ShellBottomPanelState,
|
||||
pub viewport: ShellViewportState,
|
||||
pub navigation: ShellNavigationState,
|
||||
pub panels: ShellPanelState,
|
||||
pub chrome: ShellChromeState,
|
||||
pub workspace: ShellWorkspaceState,
|
||||
}
|
||||
|
||||
pub(in crate::features) struct ShellFeatureInit {
|
||||
pub bottom_panel_mode: BottomPanelMode,
|
||||
pub quick_commands_height: f32,
|
||||
pub command_send_height: f32,
|
||||
pub active_left_panel: Option<NavItem>,
|
||||
pub active_right_panel: Option<NavItem>,
|
||||
pub left_open_panels: Vec<String>,
|
||||
pub right_open_panels: Vec<String>,
|
||||
pub panel_stack_sizes: HashMap<String, f32>,
|
||||
pub panel_multi_open: bool,
|
||||
pub left_sidebar_collapsed: bool,
|
||||
pub right_inspector_collapsed: bool,
|
||||
pub left_panel_width: f32,
|
||||
pub right_panel_width: f32,
|
||||
pub activity_bar_layout: ActivityBarLayoutState,
|
||||
}
|
||||
|
||||
pub(in crate::features) struct ShellBottomPanelState {
|
||||
@@ -19,19 +51,145 @@ pub(in crate::features) struct ShellBottomPanelState {
|
||||
pub resize: Option<BottomPanelResizeState>,
|
||||
}
|
||||
|
||||
/// Window geometry and viewport-derived caches.
|
||||
pub(in crate::features) struct ShellViewportState {
|
||||
pub size: (f32, f32),
|
||||
pub wallpaper_tile_dimensions: Option<(String, u32, u32)>,
|
||||
pub last_change_at: Option<Instant>,
|
||||
pub title_drag_active_until: Option<Instant>,
|
||||
}
|
||||
|
||||
/// Top-level page navigation and the settings-page/window lifecycle.
|
||||
pub(in crate::features) struct ShellNavigationState {
|
||||
pub selected_nav: NavItem,
|
||||
pub main_mode: MainMode,
|
||||
pub settings: ShellSettingsNavigationState,
|
||||
}
|
||||
|
||||
pub(in crate::features) struct ShellSettingsNavigationState {
|
||||
pub active_tab: SettingsTab,
|
||||
pub expanded_groups: HashSet<String>,
|
||||
pub draft_snapshot: Option<SettingsDraftSnapshot>,
|
||||
pub window: Option<WindowHandle<SettingsWindow>>,
|
||||
pub window_open_pending: bool,
|
||||
pub previous_left_collapsed: Option<bool>,
|
||||
pub previous_right_collapsed: Option<bool>,
|
||||
}
|
||||
|
||||
/// Side-panel selection, stack layout and resize interaction state.
|
||||
pub(in crate::features) struct ShellPanelState {
|
||||
pub active_left: Option<NavItem>,
|
||||
pub active_right: Option<NavItem>,
|
||||
pub left_open: Vec<String>,
|
||||
pub right_open: Vec<String>,
|
||||
pub stack_sizes: HashMap<String, f32>,
|
||||
pub multi_open: bool,
|
||||
pub right_focus: RightFocus,
|
||||
pub left_collapsed: bool,
|
||||
pub right_collapsed: bool,
|
||||
pub mobile_left_open: bool,
|
||||
pub mobile_right_open: bool,
|
||||
pub left_width: f32,
|
||||
pub right_width: f32,
|
||||
pub resize: Option<PanelResizeState>,
|
||||
pub stack_resize: Option<PanelStackResizeState>,
|
||||
}
|
||||
|
||||
/// Activity bar, title menus, tab-strip menus and connection-failure chrome.
|
||||
pub(in crate::features) struct ShellChromeState {
|
||||
pub activity_bar_layout: ActivityBarLayoutState,
|
||||
pub activity_bar_context_menu: Option<ActivityBarContextMenuState>,
|
||||
pub title_menu_open: Option<TitleMenu>,
|
||||
pub title_menu_submenu: Option<TitleMenuSubmenu>,
|
||||
pub header_status: HeaderStatusState,
|
||||
pub open_tabs_menu_open: bool,
|
||||
pub new_session_menu_open: bool,
|
||||
pub new_session_all_sessions_open: bool,
|
||||
pub new_session_group_menu_path: Vec<String>,
|
||||
pub session_tab_strip_scroll: ScrollHandle,
|
||||
pub session_tab_scroll_into_view_pending: bool,
|
||||
pub last_connect_failure_name: Option<String>,
|
||||
pub last_connect_failure_error: Option<String>,
|
||||
}
|
||||
|
||||
/// Global and per-tab pane trees for the workspace surface.
|
||||
pub(in crate::features) struct ShellWorkspaceState {
|
||||
pub split: Option<WorkspaceSplitState>,
|
||||
pub split_resize: Option<WorkspaceSplitResizeState>,
|
||||
pub pane_roots: HashMap<String, WorkspacePaneNode>,
|
||||
pub tab_owner: HashMap<String, String>,
|
||||
pub focused_terminal_leaf_id: Option<String>,
|
||||
pub pane_layout_restored: bool,
|
||||
}
|
||||
|
||||
impl ShellFeatureState {
|
||||
pub(in crate::features) fn new(
|
||||
bottom_panel_mode: BottomPanelMode,
|
||||
quick_commands_height: f32,
|
||||
command_send_height: f32,
|
||||
) -> Self {
|
||||
pub(in crate::features) fn new(init: ShellFeatureInit) -> Self {
|
||||
Self {
|
||||
bottom_panel: ShellBottomPanelState {
|
||||
mode: bottom_panel_mode,
|
||||
quick_commands_height,
|
||||
command_send_height,
|
||||
mode: init.bottom_panel_mode,
|
||||
quick_commands_height: init.quick_commands_height,
|
||||
command_send_height: init.command_send_height,
|
||||
resize: None,
|
||||
},
|
||||
viewport: ShellViewportState {
|
||||
size: (1280., 800.),
|
||||
wallpaper_tile_dimensions: None,
|
||||
last_change_at: None,
|
||||
title_drag_active_until: None,
|
||||
},
|
||||
navigation: ShellNavigationState {
|
||||
selected_nav: NavItem::Workspace,
|
||||
main_mode: MainMode::Workspace,
|
||||
settings: ShellSettingsNavigationState {
|
||||
active_tab: SettingsTab::General,
|
||||
expanded_groups: HashSet::from(["workspace".to_string()]),
|
||||
draft_snapshot: None,
|
||||
window: None,
|
||||
window_open_pending: false,
|
||||
previous_left_collapsed: None,
|
||||
previous_right_collapsed: None,
|
||||
},
|
||||
},
|
||||
panels: ShellPanelState {
|
||||
active_left: init.active_left_panel,
|
||||
active_right: init.active_right_panel,
|
||||
left_open: init.left_open_panels,
|
||||
right_open: init.right_open_panels,
|
||||
stack_sizes: init.panel_stack_sizes,
|
||||
multi_open: init.panel_multi_open,
|
||||
right_focus: RightFocus::Default,
|
||||
left_collapsed: init.left_sidebar_collapsed,
|
||||
right_collapsed: init.right_inspector_collapsed,
|
||||
mobile_left_open: false,
|
||||
mobile_right_open: false,
|
||||
left_width: init.left_panel_width,
|
||||
right_width: init.right_panel_width,
|
||||
resize: None,
|
||||
stack_resize: None,
|
||||
},
|
||||
chrome: ShellChromeState {
|
||||
activity_bar_layout: init.activity_bar_layout,
|
||||
activity_bar_context_menu: None,
|
||||
title_menu_open: None,
|
||||
title_menu_submenu: None,
|
||||
header_status: HeaderStatusState::default(),
|
||||
open_tabs_menu_open: false,
|
||||
new_session_menu_open: false,
|
||||
new_session_all_sessions_open: false,
|
||||
new_session_group_menu_path: Vec::new(),
|
||||
session_tab_strip_scroll: ScrollHandle::new(),
|
||||
session_tab_scroll_into_view_pending: false,
|
||||
last_connect_failure_name: None,
|
||||
last_connect_failure_error: None,
|
||||
},
|
||||
workspace: ShellWorkspaceState {
|
||||
split: None,
|
||||
split_resize: None,
|
||||
pane_roots: HashMap::new(),
|
||||
tab_owner: HashMap::new(),
|
||||
focused_terminal_leaf_id: None,
|
||||
pane_layout_restored: false,
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -77,16 +235,225 @@ impl ShellBottomPanelState {
|
||||
}
|
||||
}
|
||||
|
||||
impl ShellViewportState {
|
||||
pub(in crate::features) fn update_size(&mut self, size: (f32, f32), now: Instant) -> bool {
|
||||
if self.size == size {
|
||||
return false;
|
||||
}
|
||||
self.size = size;
|
||||
self.last_change_at = Some(now);
|
||||
true
|
||||
}
|
||||
|
||||
pub(in crate::features) fn mark_title_drag(&mut self, now: Instant, hold: Duration) {
|
||||
self.title_drag_active_until = Some(now + hold);
|
||||
}
|
||||
|
||||
pub(in crate::features) fn title_drag_active(&self, now: Instant) -> bool {
|
||||
self.title_drag_active_until
|
||||
.is_some_and(|until| now < until)
|
||||
}
|
||||
}
|
||||
|
||||
impl ShellPanelState {
|
||||
const LEFT_WIDTH_MIN: f32 = 160.;
|
||||
const LEFT_WIDTH_MAX: f32 = 720.;
|
||||
const RIGHT_WIDTH_MIN: f32 = 200.;
|
||||
const RIGHT_WIDTH_MAX: f32 = 720.;
|
||||
|
||||
pub(in crate::features) fn start_resize(&mut self, side: PanelResizeSide, start_x: Pixels) {
|
||||
let start_width = match side {
|
||||
PanelResizeSide::Left => self.left_width,
|
||||
PanelResizeSide::Right => self.right_width,
|
||||
};
|
||||
self.resize = Some(PanelResizeState {
|
||||
side,
|
||||
start_x,
|
||||
start_width: gpui::px(start_width),
|
||||
});
|
||||
}
|
||||
|
||||
pub(in crate::features) fn update_resize(
|
||||
&mut self,
|
||||
current_x: Pixels,
|
||||
) -> Option<(PanelResizeSide, f32)> {
|
||||
let state = self.resize?;
|
||||
let delta = f32::from(current_x - state.start_x);
|
||||
let start = f32::from(state.start_width);
|
||||
let width = match state.side {
|
||||
PanelResizeSide::Left => {
|
||||
self.left_width = (start + delta).clamp(Self::LEFT_WIDTH_MIN, Self::LEFT_WIDTH_MAX);
|
||||
self.left_width
|
||||
}
|
||||
PanelResizeSide::Right => {
|
||||
self.right_width =
|
||||
(start - delta).clamp(Self::RIGHT_WIDTH_MIN, Self::RIGHT_WIDTH_MAX);
|
||||
self.right_width
|
||||
}
|
||||
};
|
||||
Some((state.side, width))
|
||||
}
|
||||
|
||||
pub(in crate::features) fn finish_resize(&mut self) -> bool {
|
||||
self.resize.take().is_some()
|
||||
}
|
||||
|
||||
pub(in crate::features) fn stack_weight(&self, panel_id: &str) -> f32 {
|
||||
self.stack_sizes
|
||||
.get(panel_id)
|
||||
.copied()
|
||||
.filter(|value| value.is_finite() && *value > 0.)
|
||||
.unwrap_or(1.)
|
||||
}
|
||||
|
||||
pub(in crate::features) fn start_stack_resize(
|
||||
&mut self,
|
||||
side: PanelSide,
|
||||
above_id: String,
|
||||
below_id: String,
|
||||
start_y: Pixels,
|
||||
container_height: f32,
|
||||
) {
|
||||
self.stack_resize = Some(PanelStackResizeState {
|
||||
side,
|
||||
above_weight: self.stack_weight(&above_id),
|
||||
below_weight: self.stack_weight(&below_id),
|
||||
above_id,
|
||||
below_id,
|
||||
start_y,
|
||||
container_height: container_height.max(1.),
|
||||
});
|
||||
}
|
||||
|
||||
pub(in crate::features) fn update_stack_resize(&mut self, current_y: Pixels) -> bool {
|
||||
let Some(state) = self.stack_resize.as_ref() else {
|
||||
return false;
|
||||
};
|
||||
let delta_px = f32::from(current_y - state.start_y);
|
||||
let pair = state.above_weight + state.below_weight;
|
||||
if pair <= 0. || state.container_height <= 0. {
|
||||
return false;
|
||||
}
|
||||
let px_per_weight = state.container_height / pair;
|
||||
let min_weight = (48. / px_per_weight).min(pair / 2.).max(0.05);
|
||||
let next_above =
|
||||
(state.above_weight + delta_px / px_per_weight).clamp(min_weight, pair - min_weight);
|
||||
let next_below = pair - next_above;
|
||||
self.stack_sizes.insert(state.above_id.clone(), next_above);
|
||||
self.stack_sizes.insert(state.below_id.clone(), next_below);
|
||||
true
|
||||
}
|
||||
|
||||
pub(in crate::features) fn finish_stack_resize(&mut self) -> bool {
|
||||
self.stack_resize.take().is_some()
|
||||
}
|
||||
}
|
||||
|
||||
impl ShellChromeState {
|
||||
pub(in crate::features) fn prepare_session_switch(&mut self) {
|
||||
self.open_tabs_menu_open = false;
|
||||
self.close_new_session_menu();
|
||||
self.session_tab_scroll_into_view_pending = true;
|
||||
}
|
||||
|
||||
pub(in crate::features) fn toggle_open_tabs_menu(&mut self) {
|
||||
self.open_tabs_menu_open = !self.open_tabs_menu_open;
|
||||
if self.open_tabs_menu_open {
|
||||
self.close_new_session_menu();
|
||||
self.title_menu_open = None;
|
||||
}
|
||||
}
|
||||
|
||||
pub(in crate::features) fn close_open_tabs_menu(&mut self) -> bool {
|
||||
std::mem::take(&mut self.open_tabs_menu_open)
|
||||
}
|
||||
|
||||
pub(in crate::features) fn toggle_new_session_menu(&mut self) {
|
||||
self.new_session_menu_open = !self.new_session_menu_open;
|
||||
if self.new_session_menu_open {
|
||||
self.open_tabs_menu_open = false;
|
||||
self.title_menu_open = None;
|
||||
}
|
||||
self.new_session_all_sessions_open = false;
|
||||
self.new_session_group_menu_path.clear();
|
||||
}
|
||||
|
||||
pub(in crate::features) fn close_new_session_menu(&mut self) -> bool {
|
||||
let changed = self.new_session_menu_open
|
||||
|| self.new_session_all_sessions_open
|
||||
|| !self.new_session_group_menu_path.is_empty();
|
||||
self.new_session_menu_open = false;
|
||||
self.new_session_all_sessions_open = false;
|
||||
self.new_session_group_menu_path.clear();
|
||||
changed
|
||||
}
|
||||
}
|
||||
|
||||
impl ShellWorkspaceState {
|
||||
pub(in crate::features) fn rebuild_tab_owners(&mut self) {
|
||||
let mut owners = HashMap::new();
|
||||
for (tab_root, tree) in &self.pane_roots {
|
||||
for leaf in tree.session_ids() {
|
||||
owners.insert(leaf, tab_root.clone());
|
||||
}
|
||||
}
|
||||
self.tab_owner = owners;
|
||||
}
|
||||
|
||||
pub(in crate::features) fn replace_session_id(&mut self, old_id: &str, new_id: &str) {
|
||||
for root in self.pane_roots.values_mut() {
|
||||
root.replace_session_id(old_id, new_id);
|
||||
}
|
||||
if let Some(root) = self.pane_roots.remove(old_id) {
|
||||
self.pane_roots.insert(new_id.to_string(), root);
|
||||
}
|
||||
if let Some(root) = self.split.as_mut() {
|
||||
root.replace_session_id(old_id, new_id);
|
||||
}
|
||||
self.rebuild_tab_owners();
|
||||
}
|
||||
|
||||
pub(in crate::features) fn remove_session(&mut self, session_id: &str) {
|
||||
self.tab_owner.remove(session_id);
|
||||
self.pane_roots.remove(session_id);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::collections::HashMap;
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
use gpui::px;
|
||||
|
||||
use super::ShellFeatureState;
|
||||
use crate::models::BottomPanelMode;
|
||||
use super::{ShellFeatureInit, ShellFeatureState};
|
||||
use crate::models::{
|
||||
ActivityBarLayoutState, BottomPanelMode, PanelResizeSide, PanelSide, WorkspacePaneNode,
|
||||
WorkspaceSplitDirection,
|
||||
};
|
||||
|
||||
fn shell(mode: BottomPanelMode) -> ShellFeatureState {
|
||||
ShellFeatureState::new(ShellFeatureInit {
|
||||
bottom_panel_mode: mode,
|
||||
quick_commands_height: 120.,
|
||||
command_send_height: 180.,
|
||||
active_left_panel: None,
|
||||
active_right_panel: None,
|
||||
left_open_panels: Vec::new(),
|
||||
right_open_panels: Vec::new(),
|
||||
panel_stack_sizes: HashMap::new(),
|
||||
panel_multi_open: false,
|
||||
left_sidebar_collapsed: true,
|
||||
right_inspector_collapsed: true,
|
||||
left_panel_width: 240.,
|
||||
right_panel_width: 320.,
|
||||
activity_bar_layout: ActivityBarLayoutState::default(),
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn bottom_panel_resize_updates_only_the_mode_that_started_the_drag() {
|
||||
let mut shell = ShellFeatureState::new(BottomPanelMode::QuickCommands, 120., 180.);
|
||||
let mut shell = shell(BottomPanelMode::QuickCommands);
|
||||
|
||||
assert!(shell.bottom_panel.start_resize(px(400.)));
|
||||
shell.bottom_panel.mode = BottomPanelMode::CommandSend;
|
||||
@@ -99,9 +466,109 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn hidden_bottom_panel_does_not_start_resize() {
|
||||
let mut shell = ShellFeatureState::new(BottomPanelMode::Hidden, 120., 180.);
|
||||
let mut shell = shell(BottomPanelMode::Hidden);
|
||||
|
||||
assert!(!shell.bottom_panel.start_resize(px(400.)));
|
||||
assert!(shell.bottom_panel.resize.is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn panel_resize_clamps_each_side_and_finishes_once() {
|
||||
let mut shell = shell(BottomPanelMode::Hidden);
|
||||
|
||||
shell.panels.start_resize(PanelResizeSide::Left, px(100.));
|
||||
assert_eq!(
|
||||
shell.panels.update_resize(px(-100.)),
|
||||
Some((PanelResizeSide::Left, 160.))
|
||||
);
|
||||
assert!(shell.panels.finish_resize());
|
||||
assert!(!shell.panels.finish_resize());
|
||||
|
||||
shell.panels.start_resize(PanelResizeSide::Right, px(100.));
|
||||
assert_eq!(
|
||||
shell.panels.update_resize(px(-500.)),
|
||||
Some((PanelResizeSide::Right, 720.))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn panel_stack_resize_preserves_pair_weight() {
|
||||
let mut shell = shell(BottomPanelMode::Hidden);
|
||||
shell.panels.stack_sizes.insert("above".to_string(), 2.);
|
||||
shell.panels.stack_sizes.insert("below".to_string(), 1.);
|
||||
shell.panels.start_stack_resize(
|
||||
PanelSide::Left,
|
||||
"above".to_string(),
|
||||
"below".to_string(),
|
||||
px(100.),
|
||||
300.,
|
||||
);
|
||||
|
||||
assert!(shell.panels.update_stack_resize(px(150.)));
|
||||
let total = shell.panels.stack_sizes["above"] + shell.panels.stack_sizes["below"];
|
||||
assert!((total - 3.).abs() < f32::EPSILON);
|
||||
assert!(shell.panels.finish_stack_resize());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn chrome_menu_transitions_are_mutually_exclusive() {
|
||||
let mut shell = shell(BottomPanelMode::Hidden);
|
||||
shell.chrome.new_session_menu_open = true;
|
||||
shell.chrome.new_session_all_sessions_open = true;
|
||||
shell
|
||||
.chrome
|
||||
.new_session_group_menu_path
|
||||
.push("group".to_string());
|
||||
|
||||
shell.chrome.toggle_open_tabs_menu();
|
||||
assert!(shell.chrome.open_tabs_menu_open);
|
||||
assert!(!shell.chrome.new_session_menu_open);
|
||||
assert!(!shell.chrome.new_session_all_sessions_open);
|
||||
assert!(shell.chrome.new_session_group_menu_path.is_empty());
|
||||
|
||||
shell.chrome.toggle_new_session_menu();
|
||||
assert!(!shell.chrome.open_tabs_menu_open);
|
||||
assert!(shell.chrome.new_session_menu_open);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn viewport_tracks_only_real_geometry_changes_and_title_drag_deadline() {
|
||||
let mut shell = shell(BottomPanelMode::Hidden);
|
||||
let now = Instant::now();
|
||||
assert!(!shell.viewport.update_size((1280., 800.), now));
|
||||
assert!(shell.viewport.update_size((1024., 768.), now));
|
||||
assert_eq!(shell.viewport.last_change_at, Some(now));
|
||||
|
||||
shell
|
||||
.viewport
|
||||
.mark_title_drag(now, Duration::from_millis(10));
|
||||
assert!(shell.viewport.title_drag_active(now));
|
||||
assert!(
|
||||
!shell
|
||||
.viewport
|
||||
.title_drag_active(now + Duration::from_millis(10))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn workspace_rebuilds_and_renames_tab_ownership() {
|
||||
let mut shell = shell(BottomPanelMode::Hidden);
|
||||
shell.workspace.pane_roots.insert(
|
||||
"root".to_string(),
|
||||
WorkspacePaneNode::Split {
|
||||
id: "split".to_string(),
|
||||
direction: WorkspaceSplitDirection::Vertical,
|
||||
ratio_percent: 50,
|
||||
first: Box::new(WorkspacePaneNode::leaf("root".to_string())),
|
||||
second: Box::new(WorkspacePaneNode::leaf("leaf".to_string())),
|
||||
},
|
||||
);
|
||||
shell.workspace.rebuild_tab_owners();
|
||||
assert_eq!(shell.workspace.tab_owner["leaf"], "root");
|
||||
|
||||
shell.workspace.replace_session_id("root", "renamed");
|
||||
assert!(shell.workspace.pane_roots.contains_key("renamed"));
|
||||
assert_eq!(shell.workspace.tab_owner["leaf"], "renamed");
|
||||
assert_eq!(shell.workspace.tab_owner["renamed"], "renamed");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,7 +25,7 @@ impl NyaTermApp {
|
||||
.collect::<Vec<_>>();
|
||||
if live_ids.is_empty() {
|
||||
self.terminal.windows.tree = None;
|
||||
self.focused_terminal_window_leaf_id = None;
|
||||
self.shell.workspace.focused_terminal_leaf_id = None;
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -44,7 +44,7 @@ impl NyaTermApp {
|
||||
}
|
||||
|
||||
if let Some(root) = self.terminal.windows.tree.as_mut() {
|
||||
let preferred = self.focused_terminal_window_leaf_id.clone();
|
||||
let preferred = self.shell.workspace.focused_terminal_leaf_id.clone();
|
||||
for tab_id in &live_ids {
|
||||
root.ensure_tab(tab_id, preferred.as_deref());
|
||||
}
|
||||
@@ -52,11 +52,13 @@ impl NyaTermApp {
|
||||
let _ = root.set_active_tab(&active);
|
||||
}
|
||||
if self
|
||||
.focused_terminal_window_leaf_id
|
||||
.shell
|
||||
.workspace
|
||||
.focused_terminal_leaf_id
|
||||
.as_ref()
|
||||
.is_none_or(|id| !root.leaf_ids().iter().any(|leaf| leaf == id))
|
||||
{
|
||||
self.focused_terminal_window_leaf_id = root.first_leaf_id();
|
||||
self.shell.workspace.focused_terminal_leaf_id = root.first_leaf_id();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -75,7 +77,7 @@ impl NyaTermApp {
|
||||
}
|
||||
let active = self.session.active_id.clone();
|
||||
let root = TerminalWindowNode::leaf(tab_ids, active);
|
||||
self.focused_terminal_window_leaf_id = root.first_leaf_id();
|
||||
self.shell.workspace.focused_terminal_leaf_id = root.first_leaf_id();
|
||||
self.terminal.windows.tree = Some(root);
|
||||
}
|
||||
|
||||
@@ -90,10 +92,10 @@ impl NyaTermApp {
|
||||
let _ = set_leaf_active(root, &leaf_id, &session_id);
|
||||
let _ = root.set_active_tab(&session_id);
|
||||
}
|
||||
self.focused_terminal_window_leaf_id = Some(leaf_id);
|
||||
self.shell.workspace.focused_terminal_leaf_id = Some(leaf_id);
|
||||
self.activate_session_id_with_surface_sync(&session_id, cx);
|
||||
self.selected_nav = NavItem::Workspace;
|
||||
self.main_mode = MainMode::Workspace;
|
||||
self.shell.navigation.selected_nav = NavItem::Workspace;
|
||||
self.shell.navigation.main_mode = MainMode::Workspace;
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
@@ -115,7 +117,7 @@ impl NyaTermApp {
|
||||
};
|
||||
let _ = root.set_active_tab(&tab_id);
|
||||
if let Some(leaf_id) = find_leaf_with_tab(root, &tab_id) {
|
||||
self.focused_terminal_window_leaf_id = Some(leaf_id);
|
||||
self.shell.workspace.focused_terminal_leaf_id = Some(leaf_id);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -131,7 +133,7 @@ impl NyaTermApp {
|
||||
};
|
||||
if root.place_tab_before(&tab_id, &before_tab_id) {
|
||||
let _ = root.set_active_tab(&tab_id);
|
||||
self.focused_terminal_window_leaf_id =
|
||||
self.shell.workspace.focused_terminal_leaf_id =
|
||||
find_leaf_with_tab(root, &tab_id).or_else(|| root.first_leaf_id());
|
||||
self.activate_session_id_with_surface_sync(&tab_id, cx);
|
||||
self.terminal.view.status = format!(
|
||||
@@ -188,11 +190,11 @@ impl NyaTermApp {
|
||||
return;
|
||||
}
|
||||
let _ = root.set_active_tab(&tab_id);
|
||||
self.focused_terminal_window_leaf_id =
|
||||
self.shell.workspace.focused_terminal_leaf_id =
|
||||
find_leaf_with_tab(root, &tab_id).or_else(|| root.first_leaf_id());
|
||||
self.activate_session_id_with_surface_sync(&tab_id, cx);
|
||||
self.selected_nav = NavItem::Workspace;
|
||||
self.main_mode = MainMode::Workspace;
|
||||
self.shell.navigation.selected_nav = NavItem::Workspace;
|
||||
self.shell.navigation.main_mode = MainMode::Workspace;
|
||||
let zone_label = match zone {
|
||||
TabDockZone::Center => "merged into leaf".to_string(),
|
||||
TabDockZone::Edge(edge) => format!("split to {}", edge.label()),
|
||||
@@ -224,20 +226,20 @@ impl NyaTermApp {
|
||||
return;
|
||||
};
|
||||
// Clear global pane splits so multi-leaf rendering takes precedence cleanly.
|
||||
self.workspace_split = None;
|
||||
self.workspace_split_resize = None;
|
||||
self.shell.workspace.split = None;
|
||||
self.shell.workspace.split_resize = None;
|
||||
if let Some(active) = self.session.active_id.clone() {
|
||||
let mut root = layout;
|
||||
let _ = root.set_active_tab(&active);
|
||||
self.focused_terminal_window_leaf_id =
|
||||
self.shell.workspace.focused_terminal_leaf_id =
|
||||
find_leaf_with_tab(&root, &active).or_else(|| root.first_leaf_id());
|
||||
self.terminal.windows.tree = Some(root);
|
||||
} else {
|
||||
self.focused_terminal_window_leaf_id = layout.first_leaf_id();
|
||||
self.shell.workspace.focused_terminal_leaf_id = layout.first_leaf_id();
|
||||
self.terminal.windows.tree = Some(layout);
|
||||
}
|
||||
self.selected_nav = NavItem::Workspace;
|
||||
self.main_mode = MainMode::Workspace;
|
||||
self.shell.navigation.selected_nav = NavItem::Workspace;
|
||||
self.shell.navigation.main_mode = MainMode::Workspace;
|
||||
self.terminal.view.status = format!("applied {}", mode.label().to_ascii_lowercase());
|
||||
self.persist_terminal_window_layout();
|
||||
// Global pane layout is obsolete while multi-leaf is active.
|
||||
@@ -296,11 +298,11 @@ impl NyaTermApp {
|
||||
if !matches!(restored, TerminalWindowNode::Split { .. }) {
|
||||
return;
|
||||
}
|
||||
self.focused_terminal_window_leaf_id = restored.first_leaf_id();
|
||||
self.shell.workspace.focused_terminal_leaf_id = restored.first_leaf_id();
|
||||
if let Some(active) = self.session.active_id.clone() {
|
||||
let mut root = restored;
|
||||
let _ = root.set_active_tab(&active);
|
||||
self.focused_terminal_window_leaf_id =
|
||||
self.shell.workspace.focused_terminal_leaf_id =
|
||||
find_leaf_with_tab(&root, &active).or_else(|| root.first_leaf_id());
|
||||
self.terminal.windows.tree = Some(root);
|
||||
} else {
|
||||
|
||||
@@ -23,10 +23,10 @@ impl NyaTermApp {
|
||||
|
||||
pub(in crate::features) fn prune_workspace_split(&mut self) {
|
||||
let live_ids = self.live_session_ids_with_disconnected();
|
||||
let before_roots = self.session_pane_roots.clone();
|
||||
let root_keys: Vec<String> = self.session_pane_roots.keys().cloned().collect();
|
||||
let before_roots = self.shell.workspace.pane_roots.clone();
|
||||
let root_keys: Vec<String> = self.shell.workspace.pane_roots.keys().cloned().collect();
|
||||
for tab_root in root_keys {
|
||||
let Some(root) = self.session_pane_roots.remove(&tab_root) else {
|
||||
let Some(root) = self.shell.workspace.pane_roots.remove(&tab_root) else {
|
||||
continue;
|
||||
};
|
||||
match root.prune(&live_ids) {
|
||||
@@ -41,7 +41,7 @@ impl NyaTermApp {
|
||||
.next()
|
||||
.unwrap_or_else(|| tab_root.clone())
|
||||
};
|
||||
self.session_pane_roots.insert(key, node);
|
||||
self.shell.workspace.pane_roots.insert(key, node);
|
||||
}
|
||||
// Single leaf collapses to no stored tree for this tab.
|
||||
}
|
||||
@@ -49,13 +49,13 @@ impl NyaTermApp {
|
||||
}
|
||||
}
|
||||
// Also prune legacy workspace_split if roots empty (migration path).
|
||||
if self.session_pane_roots.is_empty() {
|
||||
if let Some(root) = self.workspace_split.take() {
|
||||
if self.shell.workspace.pane_roots.is_empty() {
|
||||
if let Some(root) = self.shell.workspace.split.take() {
|
||||
match root.prune(&live_ids) {
|
||||
Some(node) => {
|
||||
if node.is_split() {
|
||||
if let Some(first) = node.session_ids().into_iter().next() {
|
||||
self.session_pane_roots.insert(first.clone(), node);
|
||||
self.shell.workspace.pane_roots.insert(first.clone(), node);
|
||||
}
|
||||
} else if let WorkspacePaneNode::Leaf { session_id } = node {
|
||||
if self.session.active_id.is_none() {
|
||||
@@ -69,7 +69,7 @@ impl NyaTermApp {
|
||||
}
|
||||
self.rebuild_session_tab_owners();
|
||||
self.sync_workspace_split_from_active_tab();
|
||||
if self.session_pane_roots != before_roots {
|
||||
if self.shell.workspace.pane_roots != before_roots {
|
||||
self.persist_workspace_pane_layout();
|
||||
if self.startup_restore_complete {
|
||||
self.persist_open_tabs();
|
||||
@@ -89,29 +89,21 @@ impl NyaTermApp {
|
||||
|
||||
/// Rebuild leaf→tab-root ownership from `session_pane_roots`.
|
||||
pub(in crate::features) fn rebuild_session_tab_owners(&mut self) {
|
||||
self.session_tab_owner.clear();
|
||||
let roots: Vec<(String, WorkspacePaneNode)> = self
|
||||
.session_pane_roots
|
||||
.iter()
|
||||
.map(|(k, v)| (k.clone(), v.clone()))
|
||||
.collect();
|
||||
for (tab_root, tree) in roots {
|
||||
for leaf in tree.session_ids() {
|
||||
self.session_tab_owner.insert(leaf, tab_root.clone());
|
||||
}
|
||||
}
|
||||
self.shell.workspace.rebuild_tab_owners();
|
||||
}
|
||||
|
||||
/// Expose the active tab's pane tree via `workspace_split` for existing renderers.
|
||||
pub(in crate::features) fn sync_workspace_split_from_active_tab(&mut self) {
|
||||
let Some(active) = self.session.active_id.clone() else {
|
||||
self.workspace_split = None;
|
||||
self.shell.workspace.split = None;
|
||||
self.sync_terminal_frame_snapshot_priority();
|
||||
return;
|
||||
};
|
||||
let tab_root = self.tab_root_for_session(&active);
|
||||
self.workspace_split = self
|
||||
.session_pane_roots
|
||||
self.shell.workspace.split = self
|
||||
.shell
|
||||
.workspace
|
||||
.pane_roots
|
||||
.get(&tab_root)
|
||||
.filter(|root| root.is_split())
|
||||
.cloned();
|
||||
@@ -123,9 +115,9 @@ impl NyaTermApp {
|
||||
return;
|
||||
};
|
||||
let tab_root = self.tab_root_for_session(&active);
|
||||
if let Some(root) = self.workspace_split.clone() {
|
||||
if let Some(root) = self.shell.workspace.split.clone() {
|
||||
if root.is_split() {
|
||||
self.session_pane_roots.insert(tab_root, root);
|
||||
self.shell.workspace.pane_roots.insert(tab_root, root);
|
||||
self.rebuild_session_tab_owners();
|
||||
}
|
||||
}
|
||||
@@ -139,7 +131,7 @@ impl NyaTermApp {
|
||||
) {
|
||||
let split_id = uuid();
|
||||
let tab_root = self.tab_root_for_session(&primary_session_id);
|
||||
if let Some(root) = self.session_pane_roots.get_mut(&tab_root) {
|
||||
if let Some(root) = self.shell.workspace.pane_roots.get_mut(&tab_root) {
|
||||
if root.split_leaf(
|
||||
&primary_session_id,
|
||||
secondary_session_id.clone(),
|
||||
@@ -149,8 +141,8 @@ impl NyaTermApp {
|
||||
self.rebuild_session_tab_owners();
|
||||
self.activate_session_id(&secondary_session_id);
|
||||
self.sync_workspace_split_from_active_tab();
|
||||
self.selected_nav = NavItem::Workspace;
|
||||
self.main_mode = MainMode::Workspace;
|
||||
self.shell.navigation.selected_nav = NavItem::Workspace;
|
||||
self.shell.navigation.main_mode = MainMode::Workspace;
|
||||
self.persist_workspace_pane_layout();
|
||||
if self.startup_restore_complete {
|
||||
self.persist_open_tabs();
|
||||
@@ -166,12 +158,12 @@ impl NyaTermApp {
|
||||
first: Box::new(WorkspacePaneNode::leaf(primary_session_id.clone())),
|
||||
second: Box::new(WorkspacePaneNode::leaf(secondary_session_id.clone())),
|
||||
};
|
||||
self.session_pane_roots.insert(tab_root, root);
|
||||
self.shell.workspace.pane_roots.insert(tab_root, root);
|
||||
self.rebuild_session_tab_owners();
|
||||
self.activate_session_id(&secondary_session_id);
|
||||
self.sync_workspace_split_from_active_tab();
|
||||
self.selected_nav = NavItem::Workspace;
|
||||
self.main_mode = MainMode::Workspace;
|
||||
self.shell.navigation.selected_nav = NavItem::Workspace;
|
||||
self.shell.navigation.main_mode = MainMode::Workspace;
|
||||
self.persist_workspace_pane_layout();
|
||||
if self.startup_restore_complete {
|
||||
self.persist_open_tabs();
|
||||
@@ -188,8 +180,8 @@ impl NyaTermApp {
|
||||
}
|
||||
self.activate_session_id_with_surface_sync(&session_id, cx);
|
||||
self.sync_workspace_split_from_active_tab();
|
||||
self.selected_nav = NavItem::Workspace;
|
||||
self.main_mode = MainMode::Workspace;
|
||||
self.shell.navigation.selected_nav = NavItem::Workspace;
|
||||
self.shell.navigation.main_mode = MainMode::Workspace;
|
||||
self.terminal.view.status = format!("focused pane {}", short_id(&session_id));
|
||||
cx.notify();
|
||||
}
|
||||
@@ -241,18 +233,18 @@ impl NyaTermApp {
|
||||
return;
|
||||
};
|
||||
let tab_root = self.tab_root_for_session(&active_id);
|
||||
let Some(root) = self.session_pane_roots.remove(&tab_root) else {
|
||||
self.workspace_split = None;
|
||||
let Some(root) = self.shell.workspace.pane_roots.remove(&tab_root) else {
|
||||
self.shell.workspace.split = None;
|
||||
self.terminal.view.status = "workspace is not split".to_string();
|
||||
cx.notify();
|
||||
return;
|
||||
};
|
||||
self.workspace_split_resize = None;
|
||||
self.shell.workspace.split_resize = None;
|
||||
|
||||
if let Some(collapsed) = collapse_around_session(root.clone(), &active_id) {
|
||||
match collapsed {
|
||||
WorkspacePaneNode::Split { .. } => {
|
||||
self.session_pane_roots.insert(tab_root, collapsed);
|
||||
self.shell.workspace.pane_roots.insert(tab_root, collapsed);
|
||||
self.terminal.view.status = "collapsed focused split".to_string();
|
||||
}
|
||||
WorkspacePaneNode::Leaf { session_id } => {
|
||||
@@ -295,7 +287,7 @@ impl NyaTermApp {
|
||||
) {
|
||||
(Some(direction), Some(start_ratio)) => (direction, start_ratio),
|
||||
_ => {
|
||||
let Some(root) = self.workspace_split.as_ref() else {
|
||||
let Some(root) = self.shell.workspace.split.as_ref() else {
|
||||
return;
|
||||
};
|
||||
let Some(direction) = root.direction_for_split(&split_id) else {
|
||||
@@ -308,7 +300,7 @@ impl NyaTermApp {
|
||||
}
|
||||
}
|
||||
} else {
|
||||
let Some(root) = self.workspace_split.as_ref() else {
|
||||
let Some(root) = self.shell.workspace.split.as_ref() else {
|
||||
return;
|
||||
};
|
||||
let Some(direction) = root.direction_for_split(&split_id) else {
|
||||
@@ -323,7 +315,7 @@ impl NyaTermApp {
|
||||
WorkspaceSplitDirection::Horizontal => event.position.y,
|
||||
WorkspaceSplitDirection::Vertical => event.position.x,
|
||||
};
|
||||
self.workspace_split_resize = Some(WorkspaceSplitResizeState {
|
||||
self.shell.workspace.split_resize = Some(WorkspaceSplitResizeState {
|
||||
split_id,
|
||||
direction,
|
||||
start_pos,
|
||||
@@ -339,7 +331,7 @@ impl NyaTermApp {
|
||||
event: &MouseMoveEvent,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
let Some(state) = self.workspace_split_resize.clone() else {
|
||||
let Some(state) = self.shell.workspace.split_resize.clone() else {
|
||||
return;
|
||||
};
|
||||
let current = match state.direction {
|
||||
@@ -365,7 +357,7 @@ impl NyaTermApp {
|
||||
}
|
||||
}
|
||||
if !applied {
|
||||
if let Some(root) = self.workspace_split.as_mut() {
|
||||
if let Some(root) = self.shell.workspace.split.as_mut() {
|
||||
if root.set_ratio_for_split(&state.split_id, next) {
|
||||
applied = true;
|
||||
self.write_back_active_tab_pane_root();
|
||||
@@ -383,7 +375,7 @@ impl NyaTermApp {
|
||||
_event: &MouseUpEvent,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
if let Some(state) = self.workspace_split_resize.take() {
|
||||
if let Some(state) = self.shell.workspace.split_resize.take() {
|
||||
let ratio = self
|
||||
.terminal
|
||||
.windows
|
||||
@@ -391,7 +383,9 @@ impl NyaTermApp {
|
||||
.as_ref()
|
||||
.and_then(|root| root.ratio_for_split(&state.split_id))
|
||||
.or_else(|| {
|
||||
self.workspace_split
|
||||
self.shell
|
||||
.workspace
|
||||
.split
|
||||
.as_ref()
|
||||
.and_then(|root| root.ratio_for_split(&state.split_id))
|
||||
});
|
||||
@@ -401,7 +395,9 @@ impl NyaTermApp {
|
||||
if self.terminal_windows_is_multi_leaf() {
|
||||
self.persist_terminal_window_layout();
|
||||
} else if self
|
||||
.workspace_split
|
||||
.shell
|
||||
.workspace
|
||||
.split
|
||||
.as_ref()
|
||||
.is_some_and(|root| root.is_split())
|
||||
{
|
||||
@@ -472,12 +468,16 @@ impl NyaTermApp {
|
||||
.map(|session| session.id)
|
||||
.collect::<Vec<_>>();
|
||||
let layout = self
|
||||
.workspace_split
|
||||
.shell
|
||||
.workspace
|
||||
.split
|
||||
.as_ref()
|
||||
.filter(|root| root.is_split())
|
||||
.and_then(|root| root.serialize_layout(&ordered))
|
||||
.or_else(|| {
|
||||
self.session_pane_roots
|
||||
self.shell
|
||||
.workspace
|
||||
.pane_roots
|
||||
.values()
|
||||
.find(|root| root.is_split())
|
||||
.and_then(|root| root.serialize_layout(&ordered))
|
||||
@@ -496,16 +496,16 @@ impl NyaTermApp {
|
||||
}
|
||||
|
||||
pub(in crate::features) fn try_restore_workspace_pane_layout(&mut self) {
|
||||
if self.workspace_pane_layout_restored {
|
||||
if self.shell.workspace.pane_layout_restored {
|
||||
return;
|
||||
}
|
||||
if !self.settings.startup_restore || !self.settings.startup_restore_window_layout {
|
||||
self.workspace_pane_layout_restored = true;
|
||||
self.shell.workspace.pane_layout_restored = true;
|
||||
return;
|
||||
}
|
||||
// Multi-leaf tab windows take visual precedence; skip pane restore when active.
|
||||
if self.terminal_windows_is_multi_leaf() {
|
||||
self.workspace_pane_layout_restored = true;
|
||||
self.shell.workspace.pane_layout_restored = true;
|
||||
return;
|
||||
}
|
||||
let ordered = self
|
||||
@@ -516,11 +516,11 @@ impl NyaTermApp {
|
||||
if ordered.len() < 2 {
|
||||
// After startup finishes, don't keep waiting forever for a second tab.
|
||||
if self.startup_restore_complete {
|
||||
self.workspace_pane_layout_restored = true;
|
||||
self.shell.workspace.pane_layout_restored = true;
|
||||
}
|
||||
return;
|
||||
}
|
||||
self.workspace_pane_layout_restored = true;
|
||||
self.shell.workspace.pane_layout_restored = true;
|
||||
let Ok(store) = ConnectionStore::open_with_portable_key_path(
|
||||
self.runtime.config_dir(),
|
||||
self.runtime.portable_key_path().map(ToOwned::to_owned),
|
||||
@@ -548,12 +548,12 @@ impl NyaTermApp {
|
||||
}
|
||||
// Install as a per-tab root under the first leaf (legacy global layout path).
|
||||
if let Some(first) = restored.session_ids().into_iter().next() {
|
||||
self.session_pane_roots.insert(first, restored);
|
||||
self.shell.workspace.pane_roots.insert(first, restored);
|
||||
self.rebuild_session_tab_owners();
|
||||
self.sync_workspace_split_from_active_tab();
|
||||
}
|
||||
self.selected_nav = NavItem::Workspace;
|
||||
self.main_mode = MainMode::Workspace;
|
||||
self.shell.navigation.selected_nav = NavItem::Workspace;
|
||||
self.shell.navigation.main_mode = MainMode::Workspace;
|
||||
self.terminal.view.status = "restored workspace pane layout".to_string();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,7 +27,7 @@ impl NyaTermApp {
|
||||
|| (!self.settings.has_master_password
|
||||
&& self.settings_master_password_draft.is_empty()))
|
||||
{
|
||||
self.settings_active_tab = SettingsTab::Security;
|
||||
self.shell.navigation.settings.active_tab = SettingsTab::Security;
|
||||
self.cloud_sync.status =
|
||||
"configure a master password before enabling cloud sync".to_string();
|
||||
self.terminal.view.status = self.cloud_sync.status.clone();
|
||||
|
||||
+1
-1
@@ -14,7 +14,7 @@ impl NyaTermApp {
|
||||
let Some(menu) = self.terminal.menus.action_link_menu.clone() else {
|
||||
return div().into_any_element();
|
||||
};
|
||||
let (viewport_w, viewport_h) = self.last_viewport_size;
|
||||
let (viewport_w, viewport_h) = self.shell.viewport.size;
|
||||
let (menu_x, menu_y) = clamp_menu_position(
|
||||
f32::from(menu.x),
|
||||
f32::from(menu.y),
|
||||
|
||||
@@ -143,7 +143,7 @@ impl NyaTermApp {
|
||||
};
|
||||
let _ = selection_link_kind;
|
||||
|
||||
let (viewport_w, viewport_h) = self.last_viewport_size;
|
||||
let (viewport_w, viewport_h) = self.shell.viewport.size;
|
||||
// Approximate height from max-h for clamp (scrollable menus can be shorter).
|
||||
let (menu_x, menu_y) = clamp_menu_position(
|
||||
f32::from(menu.x),
|
||||
|
||||
@@ -890,7 +890,7 @@ impl NyaTermApp {
|
||||
}
|
||||
|
||||
fn terminal_session_has_visible_surface(&self, session_id: &str) -> bool {
|
||||
if session_id.is_empty() || self.main_mode != MainMode::Workspace {
|
||||
if session_id.is_empty() || self.shell.navigation.main_mode != MainMode::Workspace {
|
||||
return false;
|
||||
}
|
||||
self.visible_terminal_session_ids()
|
||||
@@ -899,7 +899,7 @@ impl NyaTermApp {
|
||||
}
|
||||
|
||||
pub(in crate::features) fn visible_terminal_session_ids(&self) -> Vec<&str> {
|
||||
if self.main_mode != MainMode::Workspace {
|
||||
if self.shell.navigation.main_mode != MainMode::Workspace {
|
||||
return Vec::new();
|
||||
}
|
||||
if let Some(root) = self.terminal.windows.tree.as_ref()
|
||||
@@ -907,7 +907,7 @@ impl NyaTermApp {
|
||||
{
|
||||
return terminal_window_node_visible_tab_ids(root);
|
||||
}
|
||||
if let Some(root) = self.workspace_split.as_ref() {
|
||||
if let Some(root) = self.shell.workspace.split.as_ref() {
|
||||
return workspace_pane_node_visible_session_ids(root);
|
||||
}
|
||||
self.session.active_id.iter().map(String::as_str).collect()
|
||||
|
||||
@@ -55,7 +55,7 @@ impl NyaTermApp {
|
||||
// Tauri: closing a strip tab closes the whole tab tree; closing a secondary leaf
|
||||
// only removes that pane. Strip close uses the tab-root id.
|
||||
let close_ids = if !self.is_secondary_pane_session(&session_id) {
|
||||
if let Some(root) = self.session_pane_roots.get(&session_id) {
|
||||
if let Some(root) = self.shell.workspace.pane_roots.get(&session_id) {
|
||||
root.session_ids()
|
||||
} else {
|
||||
vec![session_id.clone()]
|
||||
|
||||
+3
-1
@@ -277,7 +277,9 @@ impl NyaTermApp {
|
||||
{
|
||||
return window_root.active_tabs();
|
||||
}
|
||||
self.workspace_split
|
||||
self.shell
|
||||
.workspace
|
||||
.split
|
||||
.as_ref()
|
||||
.map(|root| root.session_ids())
|
||||
.or_else(|| self.session.active_id.clone().map(|id| vec![id]))
|
||||
|
||||
@@ -189,7 +189,7 @@ impl NyaTermApp {
|
||||
}
|
||||
let Some(config) = self.session.active_ssh_config.clone() else {
|
||||
self.terminal.view.status = "start an SSH session first".to_string();
|
||||
self.selected_nav = NavItem::Transfers;
|
||||
self.shell.navigation.selected_nav = NavItem::Transfers;
|
||||
cx.notify();
|
||||
return;
|
||||
};
|
||||
|
||||
@@ -9,7 +9,7 @@ Last updated from the working tree on 2026-07-28.
|
||||
|
||||
| Metric | Current value | Notes |
|
||||
| --- | ---: | --- |
|
||||
| `NyaTermApp` fields | 102 | Counted from `features/app_state/mod.rs`; down from 585, still transitional. |
|
||||
| `NyaTermApp` fields | 55 | Counted from `features/app_state/mod.rs`; down from 585, still transitional. |
|
||||
| `impl NyaTermApp` blocks | 238 | Spread across 233 files under `crates/nyaterm-desktop/src`. |
|
||||
| `#[path = "..."]` declarations in desktop | 0 | Cleared. Every directory is a real module; the boundary script fails on any new occurrence. |
|
||||
| `use super::*` imports in desktop | 0 | Cleared in production and test modules; guarded crate-wide. |
|
||||
@@ -331,11 +331,17 @@ these as staged extraction candidates, not as formatting-only refactor targets.
|
||||
retains session metadata/host queries, status messages, focus and redraw
|
||||
coordination. This state remains transient and changes no session or settings
|
||||
persistence format.
|
||||
- `ShellFeatureState` is the new owner for cross-view shell interaction state,
|
||||
starting with the bottom-panel mode, quick-command/send-command heights and
|
||||
resize lifecycle. Render helpers stay on views, and `NyaTermApp` still owns
|
||||
settings persistence after a drag completes. This creates the boundary for a
|
||||
later cohesive shell-chrome cut without introducing a mirror.
|
||||
- `ShellFeatureState` now owns the complete cross-view window interaction
|
||||
cluster. Its `viewport`, `navigation`, `panels`, `chrome` and `workspace`
|
||||
children contain geometry bookkeeping, settings navigation/window state,
|
||||
side-panel stacks and resize lifecycles, title/tab menus and per-tab pane
|
||||
ownership respectively; the existing `bottom_panel` child still owns its
|
||||
mode, heights and drag state. Forty-seven top-level app fields became these
|
||||
five focused children, so `NyaTermApp` dropped from 102 fields to 55 without
|
||||
creating a writable mirror. Pure viewport, panel resize/stack resize, chrome
|
||||
menu and workspace ownership transitions execute on the child states.
|
||||
Render helpers remain on views, while `NyaTermApp` retains settings
|
||||
persistence, GPUI notification, terminal coordination and event routing.
|
||||
- The Entity Store projection layer is gone entirely, in two steps.
|
||||
|
||||
First, the six domain stores (`Ai`, `CloudSync`, `Connections`, `RemoteOps`,
|
||||
@@ -1414,7 +1420,7 @@ honest remaining list.
|
||||
compiler-confirmed final pass also removed `features/prelude.rs`, so modules
|
||||
cannot regain the same implicit dependency surface through a shared import
|
||||
bucket.
|
||||
3. Largely done. `NyaTermApp` is down from 585 fields to 102, across seventeen
|
||||
3. Largely done. `NyaTermApp` is down from 585 fields to 55, across seventeen
|
||||
feature-state structs. The latest cohesive cuts moved sixteen terminal
|
||||
command-assistance and credential-prompt fields into
|
||||
`TerminalFeatureState::assist`, then seventeen transient settings fields
|
||||
@@ -1424,9 +1430,11 @@ honest remaining list.
|
||||
conflict and GitHub device-flow state, recording and SSH-tunnel runtime
|
||||
resources with their job/UI lifecycle state, then the complete live session
|
||||
runtime with nested session-start, prompt and dialog ownership, then terminal
|
||||
presentation runtime followed by sync-input, screen-lock and bottom-panel
|
||||
interaction lifecycles. What is left is a long tail, and much of it is
|
||||
genuinely app-level (stores, runtime, services, persisted collections).
|
||||
presentation runtime followed by sync-input and screen-lock interaction
|
||||
lifecycles, and finally forty-seven window interaction fields under the
|
||||
shell's viewport, navigation, panel, chrome and workspace children. What is
|
||||
left is a long tail, and much of it is genuinely app-level (stores, runtime,
|
||||
services, persisted collections).
|
||||
Group by cohesion where a cluster exists; do not force the count down for
|
||||
its own sake.
|
||||
Method ownership is now moving too, which is what grouping the fields alone
|
||||
@@ -1434,7 +1442,7 @@ honest remaining list.
|
||||
it belongs on that state, and the `NyaTermApp` method becomes a forwarder
|
||||
that owns `cx.notify()`. That is enforced by the type system rather than by
|
||||
convention — a handler taking `&mut TransferBrowserState` cannot reach the
|
||||
session list no matter what a later edit tries. Eighty-four methods or
|
||||
session list no matter what a later edit tries. More than one hundred methods or
|
||||
self-contained transitions have moved this way across transfers, security,
|
||||
the shell, sync input, the send command bar, AI, quick commands, cloud sync,
|
||||
recording, session starts and terminal paste review;
|
||||
@@ -1445,7 +1453,9 @@ honest remaining list.
|
||||
of sync, closing a pending session start cannot update its maps without also
|
||||
applying the active pending/failed fallback rules, and paste editing cannot
|
||||
mutate its UTF-8 cursor without also clearing stale selection/IME state.
|
||||
Those are the kinds of signals to look for.
|
||||
Shell viewport timing, panel drags, mutually-exclusive tab menus and pane
|
||||
ownership rebuilding now have the same property. Those are the kinds of
|
||||
signals to look for.
|
||||
Two caveats worth keeping. Render helpers stay on the view even when they
|
||||
read one state — moving element construction onto a data struct trades one
|
||||
coupling for a worse one. And a method that reads a state plus `self.tr(...)`
|
||||
|
||||
@@ -128,6 +128,20 @@ check_no_matches \
|
||||
'^[[:space:]]*pub\(in crate::features\)[[:space:]]+(bottom_panel|quick_cmd_height|serial_send_height|bottom_panel_resize)[[:space:]]*:' \
|
||||
crates/nyaterm-desktop/src/features/app_state/mod.rs
|
||||
|
||||
check_no_matches \
|
||||
"window shell state must stay grouped under ShellFeatureState" \
|
||||
'^[[:space:]]*pub\(in crate::features\)[[:space:]]+(last_viewport_size|wallpaper_tile_dimensions|last_viewport_change_at|title_drag_active_until|selected_nav|main_mode|settings_active_tab|settings_expanded_groups|settings_draft_snapshot|settings_window|settings_window_open_pending|settings_previous_left_collapsed|settings_previous_right_collapsed|active_left_panel|active_right_panel|left_open_panels|right_open_panels|panel_stack_sizes|panel_multi_open|right_focus|left_sidebar_collapsed|right_inspector_collapsed|mobile_left_open|mobile_right_open|left_panel_width|right_panel_width|panel_resize|panel_stack_resize|activity_bar_layout|activity_bar_context_menu|title_menu_open|title_menu_submenu|header_status|open_tabs_menu_open|new_session_menu_open|new_session_all_sessions_open|new_session_group_menu_path|session_tab_strip_scroll|session_tab_scroll_into_view_pending|last_connect_failure_name|last_connect_failure_error|workspace_split|workspace_split_resize|session_pane_roots|session_tab_owner|focused_terminal_window_leaf_id|workspace_pane_layout_restored)[[:space:]]*:' \
|
||||
crates/nyaterm-desktop/src/features/app_state/mod.rs
|
||||
|
||||
check_no_matches \
|
||||
"panel resize transitions must stay on ShellPanelState" \
|
||||
'PanelResizeState' \
|
||||
crates/nyaterm-desktop/src/features/shell/panel_resize_runtime.rs
|
||||
check_no_matches \
|
||||
"panel stack resize transitions must stay on ShellPanelState" \
|
||||
'PanelStackResizeState' \
|
||||
crates/nyaterm-desktop/src/features/shell/panel_stack_runtime.rs
|
||||
|
||||
check_no_matches \
|
||||
"screen lock fields must stay grouped under SecurityFeatureState" \
|
||||
'^[[:space:]]*pub\(in crate::features\)[[:space:]]+(lock_focus|lock_password_draft|lock_status|is_locked|last_user_activity_at)[[:space:]]*:' \
|
||||
|
||||
Reference in New Issue
Block a user