mirror of
https://github.com/rust-kotlin/ashell.git
synced 2026-10-03 08:00:20 +00:00
Compare commits
6
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3c6b9e3b46 | ||
|
|
a88379b01c | ||
|
|
f322b2c717 | ||
|
|
45d0c64c06 | ||
|
|
6312a482cb | ||
|
|
8bb4cbc655 |
@@ -304,6 +304,8 @@ pub(crate) struct Ashell {
|
||||
pub(crate) runtime: Runtime,
|
||||
pub(crate) events_rx: mpsc::Receiver<BackendEvent>,
|
||||
pub(crate) events_tx: mpsc::Sender<BackendEvent>,
|
||||
pub(crate) last_window_size: Option<gpui::Size<Pixels>>,
|
||||
pub(crate) last_sidebar_width: Option<Pixels>,
|
||||
pub(crate) _subscriptions: Vec<gpui::Subscription>,
|
||||
}
|
||||
|
||||
@@ -506,6 +508,10 @@ impl Ashell {
|
||||
gpui_component::set_locale(&active_locale);
|
||||
let ui_font_family: SharedString = config.ui_font_family().into();
|
||||
let terminal_font_family: SharedString = config.terminal_font_family().into();
|
||||
let last_sidebar_width = Some(px(config
|
||||
.workspace_panels()
|
||||
.and_then(|s| s.first().copied())
|
||||
.unwrap_or(constants::SIDEBAR_WIDTH)));
|
||||
let mut this = Self {
|
||||
focus_handle: cx.focus_handle(),
|
||||
selector_focus_handle: cx.focus_handle(),
|
||||
@@ -621,6 +627,8 @@ impl Ashell {
|
||||
runtime: Runtime::new().expect("create tokio runtime"),
|
||||
events_rx,
|
||||
events_tx,
|
||||
last_window_size: None,
|
||||
last_sidebar_width,
|
||||
_subscriptions,
|
||||
};
|
||||
|
||||
@@ -1066,4 +1074,90 @@ impl Ashell {
|
||||
self.config.set_transfers(self.transfers.clone());
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
pub(crate) fn save_layout_state(&self, window: &mut gpui::Window, cx: &gpui::App) {
|
||||
if self.is_layout_reset {
|
||||
tracing::info!("[ui] layout was reset, skipping save layout state.");
|
||||
return;
|
||||
}
|
||||
let current_bounds = window.window_bounds();
|
||||
let bounds = match current_bounds {
|
||||
gpui::WindowBounds::Fullscreen(b) => b,
|
||||
gpui::WindowBounds::Maximized(b) => b,
|
||||
gpui::WindowBounds::Windowed(b) => b,
|
||||
};
|
||||
let size = bounds.size;
|
||||
if size.width.as_f32() > 400.0 && size.height.as_f32() > 300.0 {
|
||||
tracing::info!("[ui] saving layout state...");
|
||||
let mut config = ConfigStore::load().unwrap_or_else(|_| ConfigStore::in_memory());
|
||||
let saved_bounds = match current_bounds {
|
||||
gpui::WindowBounds::Fullscreen(b) => {
|
||||
crate::session::config::SavedWindowBounds::Fullscreen {
|
||||
x: b.origin.x.into(),
|
||||
y: b.origin.y.into(),
|
||||
width: b.size.width.into(),
|
||||
height: b.size.height.into(),
|
||||
}
|
||||
}
|
||||
gpui::WindowBounds::Maximized(b) => {
|
||||
let mut restore_bounds = (b.origin.x.into(), b.origin.y.into(), b.size.width.into(), b.size.height.into());
|
||||
if let Some(existing_bounds) = config.window_bounds() {
|
||||
match existing_bounds {
|
||||
crate::session::config::SavedWindowBounds::Windowed { x, y, width, height } => {
|
||||
restore_bounds = (*x, *y, *width, *height);
|
||||
}
|
||||
crate::session::config::SavedWindowBounds::Maximized { x, y, width, height } => {
|
||||
restore_bounds = (*x, *y, *width, *height);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
crate::session::config::SavedWindowBounds::Maximized {
|
||||
x: restore_bounds.0,
|
||||
y: restore_bounds.1,
|
||||
width: restore_bounds.2,
|
||||
height: restore_bounds.3,
|
||||
}
|
||||
}
|
||||
gpui::WindowBounds::Windowed(b) => {
|
||||
crate::session::config::SavedWindowBounds::Windowed {
|
||||
x: b.origin.x.into(),
|
||||
y: b.origin.y.into(),
|
||||
width: b.size.width.into(),
|
||||
height: b.size.height.into(),
|
||||
}
|
||||
}
|
||||
};
|
||||
let workspace_sizes: Vec<f32> = self.workspace_panels
|
||||
.read(cx)
|
||||
.sizes()
|
||||
.iter()
|
||||
.map(|s| s.into())
|
||||
.collect();
|
||||
let mut body_sizes: Vec<f32> = self.body_panels
|
||||
.read(cx)
|
||||
.sizes()
|
||||
.iter()
|
||||
.map(|s| s.into())
|
||||
.collect();
|
||||
|
||||
if self.sftp_panel_minimized {
|
||||
if let Some(prev) = self.prev_monitoring_size {
|
||||
if body_sizes.len() > 1 {
|
||||
body_sizes[1] = prev.into();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
config.set_layout_state(Some(saved_bounds), Some(workspace_sizes), Some(body_sizes));
|
||||
config.set_sidebar_collapsed(self.sidebar_collapsed);
|
||||
config.set_sftp_panel_minimized(self.sftp_panel_minimized);
|
||||
let _ = config.save();
|
||||
} else {
|
||||
tracing::warn!(
|
||||
"[ui] window size is too small ({:?}), skipping save layout state to prevent corrupting saved bounds.",
|
||||
size
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+4
-71
@@ -253,81 +253,14 @@ pub(crate) fn open_main_window(cx: &mut App) {
|
||||
|
||||
|
||||
|
||||
let workspace_panels_clone = view.read(cx).workspace_panels.clone();
|
||||
let body_panels_clone = view.read(cx).body_panels.clone();
|
||||
let view_clone = view.clone();
|
||||
window.on_window_should_close(cx, move |window: &mut gpui::Window, cx: &mut gpui::App| {
|
||||
if view_clone.read(cx).is_layout_reset {
|
||||
tracing::info!("[ui] layout was reset, skipping save layout state.");
|
||||
let handle = window.window_handle();
|
||||
if !cx.windows().contains(&handle) {
|
||||
tracing::warn!("[ui] window not found in app during close, skipping save layout state.");
|
||||
return true;
|
||||
}
|
||||
let current_bounds = window.window_bounds();
|
||||
let bounds = match current_bounds {
|
||||
gpui::WindowBounds::Fullscreen(b) => b,
|
||||
gpui::WindowBounds::Maximized(b) => b,
|
||||
gpui::WindowBounds::Windowed(b) => b,
|
||||
};
|
||||
let size = bounds.size;
|
||||
if size.width.as_f32() > 400.0 && size.height.as_f32() > 300.0 {
|
||||
tracing::info!("[ui] main application window closed, saving layout state...");
|
||||
let mut config = ConfigStore::load().unwrap_or_else(|_| ConfigStore::in_memory());
|
||||
let saved_bounds = match current_bounds {
|
||||
gpui::WindowBounds::Fullscreen(b) => {
|
||||
crate::session::config::SavedWindowBounds::Fullscreen {
|
||||
x: b.origin.x.into(),
|
||||
y: b.origin.y.into(),
|
||||
width: b.size.width.into(),
|
||||
height: b.size.height.into(),
|
||||
}
|
||||
}
|
||||
gpui::WindowBounds::Maximized(b) => {
|
||||
crate::session::config::SavedWindowBounds::Maximized {
|
||||
x: b.origin.x.into(),
|
||||
y: b.origin.y.into(),
|
||||
width: b.size.width.into(),
|
||||
height: b.size.height.into(),
|
||||
}
|
||||
}
|
||||
gpui::WindowBounds::Windowed(b) => {
|
||||
crate::session::config::SavedWindowBounds::Windowed {
|
||||
x: b.origin.x.into(),
|
||||
y: b.origin.y.into(),
|
||||
width: b.size.width.into(),
|
||||
height: b.size.height.into(),
|
||||
}
|
||||
}
|
||||
};
|
||||
let workspace_sizes: Vec<f32> = workspace_panels_clone
|
||||
.read(cx)
|
||||
.sizes()
|
||||
.iter()
|
||||
.map(|s| s.into())
|
||||
.collect();
|
||||
let mut body_sizes: Vec<f32> = body_panels_clone
|
||||
.read(cx)
|
||||
.sizes()
|
||||
.iter()
|
||||
.map(|s| s.into())
|
||||
.collect();
|
||||
|
||||
if view_clone.read(cx).sftp_panel_minimized {
|
||||
if let Some(prev) = view_clone.read(cx).prev_monitoring_size {
|
||||
if body_sizes.len() > 1 {
|
||||
body_sizes[1] = prev.into();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
config.set_layout_state(Some(saved_bounds), Some(workspace_sizes), Some(body_sizes));
|
||||
config.set_sidebar_collapsed(view_clone.read(cx).sidebar_collapsed);
|
||||
config.set_sftp_panel_minimized(view_clone.read(cx).sftp_panel_minimized);
|
||||
let _ = config.save();
|
||||
} else {
|
||||
tracing::warn!(
|
||||
"[ui] window size is too small ({:?}), skipping save layout state to prevent corrupting saved bounds.",
|
||||
size
|
||||
);
|
||||
}
|
||||
view_clone.read(cx).save_layout_state(window, cx);
|
||||
true
|
||||
});
|
||||
|
||||
|
||||
+100
-15
@@ -2,7 +2,7 @@ use gpui::{
|
||||
Context, ElementId, Focusable as _, FontWeight, Hsla, InteractiveElement as _, IntoElement,
|
||||
MouseButton, MouseDownEvent, ParentElement as _, PathBuilder, Pixels, Render,
|
||||
StatefulInteractiveElement as _, Styled as _, Window, canvas, div, hsla, point,
|
||||
prelude::FluentBuilder as _, px, rems, uniform_list,
|
||||
prelude::FluentBuilder as _, px, relative, rems, uniform_list,
|
||||
};
|
||||
use gpui_component::{
|
||||
ActiveTheme, Disableable as _, ElementExt, Icon, IconName, InteractiveElementExt as _, Root,
|
||||
@@ -1980,52 +1980,109 @@ impl Ashell {
|
||||
fn render_window_controls(
|
||||
&self,
|
||||
window: &mut Window,
|
||||
_cx: &mut Context<Self>,
|
||||
cx: &mut Context<Self>,
|
||||
) -> impl IntoElement {
|
||||
let is_macos = cfg!(target_os = "macos");
|
||||
let is_fullscreen = window.is_fullscreen();
|
||||
|
||||
let is_active = cx.active_window() == Some(window.window_handle());
|
||||
|
||||
h_flex()
|
||||
.group("window-controls")
|
||||
.flex_none()
|
||||
.items_center()
|
||||
.px_3()
|
||||
.gap_2()
|
||||
.when(!is_macos || is_fullscreen, |this| {
|
||||
this.child(
|
||||
div()
|
||||
h_flex()
|
||||
.id("window-close")
|
||||
.size(px(12.))
|
||||
.rounded_full()
|
||||
.bg(hsla(0.0, 0.8, 0.4, 1.0)) // Red
|
||||
.on_click(|_, window, _| window.remove_window())
|
||||
.hover(|s| s.bg(hsla(0.0, 0.8, 0.5, 1.0)))
|
||||
.active(|s| s.bg(hsla(0.0, 0.8, 0.3, 1.0))),
|
||||
.bg(if is_active { hsla(3.0 / 360.0, 1.0, 0.67, 1.0) } else { hsla(0.0, 0.0, 0.8, 1.0) }) // Red or Inactive Grey
|
||||
.group_hover("window-controls", |s| s.bg(hsla(3.0 / 360.0, 1.0, 0.67, 1.0)))
|
||||
.when(!is_macos, |this| this.window_control_area(gpui::WindowControlArea::Close))
|
||||
.on_click(cx.listener(|this, _, window, cx| {
|
||||
this.save_layout_state(window, cx);
|
||||
window.remove_window();
|
||||
}))
|
||||
.hover(|s| s.bg(hsla(3.0 / 360.0, 1.0, 0.55, 1.0)))
|
||||
.active(|s| s.bg(hsla(3.0 / 360.0, 1.0, 0.45, 1.0)))
|
||||
.items_center()
|
||||
.justify_center()
|
||||
.child(
|
||||
h_flex()
|
||||
.items_center()
|
||||
.justify_center()
|
||||
.text_size(px(7.))
|
||||
.font_weight(FontWeight::BOLD)
|
||||
.line_height(relative(1.0))
|
||||
.text_color(hsla(3.0 / 360.0, 1.0, 0.15, 0.7))
|
||||
.opacity(0.0)
|
||||
.group_hover("window-controls", |s| s.opacity(1.0))
|
||||
.child("✕")
|
||||
),
|
||||
)
|
||||
.child(
|
||||
div()
|
||||
h_flex()
|
||||
.id("window-minimize")
|
||||
.size(px(12.))
|
||||
.rounded_full()
|
||||
.bg(hsla(0.12, 0.8, 0.4, 1.0)) // Yellow
|
||||
.bg(if is_active { hsla(39.0 / 360.0, 1.0, 0.59, 1.0) } else { hsla(0.0, 0.0, 0.8, 1.0) }) // Yellow or Inactive Grey
|
||||
.group_hover("window-controls", |s| s.bg(hsla(39.0 / 360.0, 1.0, 0.59, 1.0)))
|
||||
.when(!is_macos, |this| this.window_control_area(gpui::WindowControlArea::Min))
|
||||
.on_click(|_, window, _| window.minimize_window())
|
||||
.hover(|s| s.bg(hsla(0.12, 0.8, 0.5, 1.0)))
|
||||
.active(|s| s.bg(hsla(0.12, 0.8, 0.3, 1.0))),
|
||||
.hover(|s| s.bg(hsla(39.0 / 360.0, 1.0, 0.49, 1.0)))
|
||||
.active(|s| s.bg(hsla(39.0 / 360.0, 1.0, 0.39, 1.0)))
|
||||
.items_center()
|
||||
.justify_center()
|
||||
.child(
|
||||
h_flex()
|
||||
.items_center()
|
||||
.justify_center()
|
||||
.text_size(px(7.))
|
||||
.font_weight(FontWeight::BOLD)
|
||||
.line_height(relative(1.0))
|
||||
.text_color(hsla(39.0 / 360.0, 1.0, 0.15, 0.8))
|
||||
.opacity(0.0)
|
||||
.group_hover("window-controls", |s| s.opacity(1.0))
|
||||
.child("−")
|
||||
),
|
||||
)
|
||||
.child(
|
||||
div()
|
||||
h_flex()
|
||||
.id("window-maximize")
|
||||
.size(px(12.))
|
||||
.rounded_full()
|
||||
.bg(hsla(0.33, 0.8, 0.4, 1.0)) // Green
|
||||
.bg(if is_active { hsla(127.0 / 360.0, 0.68, 0.47, 1.0) } else { hsla(0.0, 0.0, 0.8, 1.0) }) // Green or Inactive Grey
|
||||
.group_hover("window-controls", |s| s.bg(hsla(127.0 / 360.0, 0.68, 0.47, 1.0)))
|
||||
.when(!is_macos, |this| this.window_control_area(gpui::WindowControlArea::Max))
|
||||
.on_click(|_, window, _| {
|
||||
if window.is_fullscreen() {
|
||||
window.toggle_fullscreen();
|
||||
} else {
|
||||
#[cfg(target_os = "macos")]
|
||||
window.titlebar_double_click();
|
||||
#[cfg(not(target_os = "macos"))]
|
||||
window.zoom_window();
|
||||
}
|
||||
})
|
||||
.hover(|s| s.bg(hsla(0.33, 0.8, 0.5, 1.0)))
|
||||
.active(|s| s.bg(hsla(0.33, 0.8, 0.3, 1.0))),
|
||||
.hover(|s| s.bg(hsla(127.0 / 360.0, 0.68, 0.37, 1.0)))
|
||||
.active(|s| s.bg(hsla(127.0 / 360.0, 0.68, 0.27, 1.0)))
|
||||
.items_center()
|
||||
.justify_center()
|
||||
.child(
|
||||
h_flex()
|
||||
.items_center()
|
||||
.justify_center()
|
||||
.text_size(px(7.))
|
||||
.font_weight(FontWeight::BOLD)
|
||||
.line_height(relative(1.0))
|
||||
.text_color(hsla(127.0 / 360.0, 1.0, 0.15, 0.8))
|
||||
.opacity(0.0)
|
||||
.group_hover("window-controls", |s| s.opacity(1.0))
|
||||
.child("+")
|
||||
),
|
||||
)
|
||||
})
|
||||
.when(is_macos, |this| {
|
||||
@@ -2676,7 +2733,10 @@ impl Render for Ashell {
|
||||
.w_full()
|
||||
.bg(cx.theme().tab_bar)
|
||||
.on_double_click(|_, window, _| {
|
||||
#[cfg(target_os = "macos")]
|
||||
window.titlebar_double_click();
|
||||
#[cfg(not(target_os = "macos"))]
|
||||
window.zoom_window();
|
||||
})
|
||||
.child(self.render_window_controls(window, cx))
|
||||
.child(
|
||||
@@ -2883,5 +2943,30 @@ impl Render for Ashell {
|
||||
),
|
||||
)
|
||||
})
|
||||
.on_prepaint({
|
||||
let view = cx.entity().clone();
|
||||
move |_, window, cx| {
|
||||
view.update(cx, |this, cx| {
|
||||
let current_win_size = window.viewport_size();
|
||||
let size_changed = this.last_window_size.map_or(true, |prev| prev != current_win_size);
|
||||
this.last_window_size = Some(current_win_size);
|
||||
|
||||
let current_sizes = this.workspace_panels.read(cx).sizes().clone();
|
||||
if let Some(current_first_size) = current_sizes.first().copied() {
|
||||
if size_changed {
|
||||
if let Some(target_width) = this.last_sidebar_width {
|
||||
if current_first_size != target_width {
|
||||
this.workspace_panels.update(cx, |state, cx| {
|
||||
state.resize_panel(0, target_width, window, cx);
|
||||
});
|
||||
}
|
||||
}
|
||||
} else {
|
||||
this.last_sidebar_width = Some(current_first_size);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user