fix(gpui): align settings and modal interactions
This commit is contained in:
1 parent
1960e4ae40
commit
4794c162aa
9 files changed
+277
-129
No files matched your search
@@ -9,10 +9,12 @@ pub use gpui_component::{
|
||||
ActiveTheme as _, Colorize as _, Disableable as _, Icon, IconName, InteractiveElementExt as _,
|
||||
Selectable as _, Sizable as _, StyledExt as _, TitleBar,
|
||||
alert::{Alert, AlertVariant},
|
||||
button::{Button, ButtonVariants as _},
|
||||
button::{Button, ButtonVariant, ButtonVariants as _},
|
||||
checkbox::Checkbox,
|
||||
color_picker::{ColorPicker, ColorPickerEvent, ColorPickerState},
|
||||
dialog::{Dialog, DialogAction, DialogButtonProps, DialogClose, DialogFooter},
|
||||
dialog::{
|
||||
Dialog, DialogAction, DialogButtonProps, DialogClose, DialogDescription, DialogFooter,
|
||||
},
|
||||
dock::{
|
||||
DockArea, DockAreaState, DockItem, Panel, PanelControl, PanelEvent, PanelState, TabPanel,
|
||||
},
|
||||
@@ -39,10 +41,97 @@ pub use gpui_component::WindowExt as _;
|
||||
// 现有的 glob 用法不受影响。
|
||||
pub use gpui_component::{ActiveTheme, Colorize};
|
||||
|
||||
use gpui::{Window, px};
|
||||
use gpui::{ParentElement as _, SharedString, Styled as _, Window, div, px, relative};
|
||||
|
||||
/// 保留旧壳确认框的 480px 内容宽度;位置和交互采用新版 Dialog。
|
||||
pub fn center_confirm_dialog(dialog: Dialog, _window: &Window) -> Dialog {
|
||||
const DIALOG_W: f32 = 480.0;
|
||||
dialog.width(px(DIALOG_W))
|
||||
const CONFIRM_DIALOG_WIDTH: f32 = 480.0;
|
||||
const CONFIRM_DIALOG_HORIZONTAL_PADDING: f32 = 26.0;
|
||||
const CONFIRM_DIALOG_TOP_PADDING: f32 = 26.0;
|
||||
const CONFIRM_DIALOG_BOTTOM_PADDING: f32 = 20.0;
|
||||
const CONFIRM_DIALOG_BASE_HEIGHT: f32 = 130.0;
|
||||
const CONFIRM_DIALOG_BODY_LINE_HEIGHT: f32 = 24.0;
|
||||
|
||||
/// gpui-component 0.5.2 默认把 Dialog 放在视口高度的 1/10;旧 GPUI 壳则
|
||||
/// 使用 480px 内容宽度并垂直居中。这里集中恢复旧壳几何,同时保留窄窗口限幅。
|
||||
pub fn center_modal_dialog(dialog: Dialog, window: &Window, estimated_height: f32) -> Dialog {
|
||||
let viewport = window.viewport_size();
|
||||
let width = CONFIRM_DIALOG_WIDTH.min((f32::from(viewport.width) - 32.0).max(240.0));
|
||||
let margin_top = ((f32::from(viewport.height) - estimated_height) * 0.5).max(16.0);
|
||||
|
||||
dialog
|
||||
.width(px(width))
|
||||
.margin_top(px(margin_top))
|
||||
.pt(px(CONFIRM_DIALOG_TOP_PADDING))
|
||||
.pb(px(CONFIRM_DIALOG_BOTTOM_PADDING))
|
||||
.px(px(CONFIRM_DIALOG_HORIZONTAL_PADDING))
|
||||
}
|
||||
|
||||
/// 构造与旧 GPUI 壳一致的确认框。显式 footer 是必要的:固定依赖版本的普通
|
||||
/// Dialog 不会仅凭 button_props 生成按钮;DialogAction/DialogClose 负责把鼠标
|
||||
/// 点击重新汇入 Enter/Esc 共用的确认与取消回调。
|
||||
pub fn confirm_dialog(
|
||||
dialog: Dialog,
|
||||
window: &mut Window,
|
||||
title: impl Into<SharedString>,
|
||||
description: impl Into<SharedString>,
|
||||
ok_text: impl Into<SharedString>,
|
||||
cancel_text: impl Into<SharedString>,
|
||||
ok_variant: ButtonVariant,
|
||||
) -> Dialog {
|
||||
let title = title.into();
|
||||
let description = description.into();
|
||||
let ok_text = ok_text.into();
|
||||
let cancel_text = cancel_text.into();
|
||||
|
||||
let text_style = window.text_style();
|
||||
let body_width = if description.is_empty() {
|
||||
0.0
|
||||
} else {
|
||||
f32::from(
|
||||
window
|
||||
.text_system()
|
||||
.shape_line(
|
||||
description.clone(),
|
||||
px(16.0),
|
||||
&[text_style.to_run(description.len())],
|
||||
None,
|
||||
)
|
||||
.width,
|
||||
)
|
||||
};
|
||||
let available_body_width =
|
||||
(CONFIRM_DIALOG_WIDTH - CONFIRM_DIALOG_HORIZONTAL_PADDING * 2.0).max(1.0);
|
||||
let body_lines = (body_width / available_body_width).ceil().max(1.0);
|
||||
let estimated_height =
|
||||
CONFIRM_DIALOG_BASE_HEIGHT + body_lines * CONFIRM_DIALOG_BODY_LINE_HEIGHT;
|
||||
|
||||
let content_description = description.clone();
|
||||
center_modal_dialog(dialog, window, estimated_height)
|
||||
.close_button(false)
|
||||
.overlay_closable(false)
|
||||
.title(div().text_lg().font_semibold().line_height(relative(1.0)).child(title))
|
||||
.content(move |content, _, _| {
|
||||
content.child(
|
||||
DialogDescription::new()
|
||||
.text_base()
|
||||
.line_height(relative(1.5))
|
||||
.child(content_description.clone()),
|
||||
)
|
||||
})
|
||||
.footer(
|
||||
DialogFooter::new()
|
||||
.child(
|
||||
DialogClose::new()
|
||||
.child(Button::new("confirm-cancel").label(cancel_text.clone())),
|
||||
)
|
||||
.child(DialogAction::new().child(
|
||||
Button::new("confirm-ok").label(ok_text.clone()).with_variant(ok_variant),
|
||||
)),
|
||||
)
|
||||
.button_props(
|
||||
DialogButtonProps::default()
|
||||
.ok_text(ok_text)
|
||||
.ok_variant(ok_variant)
|
||||
.cancel_text(cancel_text)
|
||||
.show_cancel(true),
|
||||
)
|
||||
}
|
||||
@@ -1046,12 +1046,17 @@ impl SettingsPane {
|
||||
let pane = cx.entity().downgrade();
|
||||
window.open_dialog(cx, move |dialog, window, _cx| {
|
||||
let pane = pane.clone();
|
||||
center_confirm_dialog(dialog, window)
|
||||
.title("让背景图覆盖窗口控件区域?")
|
||||
.button_props(DialogButtonProps::default().show_cancel(true).ok_text("开启").cancel_text("取消"))
|
||||
.child(SharedString::from(
|
||||
confirm_dialog(
|
||||
dialog,
|
||||
window,
|
||||
"让背景图覆盖窗口控件区域?",
|
||||
SharedString::from(
|
||||
"背景图会延伸到标题栏、窗口按钮、Tab 与 SSH 侧栏下方,低对比度图片可能影响操作可见性;界面仍会保留最低不透明度保护。",
|
||||
))
|
||||
),
|
||||
"开启",
|
||||
"取消",
|
||||
ButtonVariant::Primary,
|
||||
)
|
||||
.on_ok(move |_, _, cx| {
|
||||
let _ = pane.update(cx, |this, cx| {
|
||||
this.persist(&[("background_image_cover_chrome", "1".to_owned())], cx);
|
||||
@@ -1145,7 +1150,15 @@ impl SettingsPane {
|
||||
// 成一次重绘,所以每帧调是安全的。
|
||||
cx.refresh_windows();
|
||||
cx.notify();
|
||||
self.schedule_slider_persist("opacity", format!("{opacity:.2}"), cx);
|
||||
// 用户拖动后 opacity 已是显式选择;同时把旧 blur=1 规范成枚举值,
|
||||
// 否则共享迁移层会再次把 blur=1 + opacity=1.00 解读成默认 0.82。
|
||||
self.schedule_slider_persist(
|
||||
vec![
|
||||
("opacity", format!("{opacity:.2}")),
|
||||
("blur", self.runtime.blur.settings_value().to_owned()),
|
||||
],
|
||||
cx,
|
||||
);
|
||||
}
|
||||
|
||||
/// 背景图透明度是**烘焙进纹理**的(decode + 长边压到 2560 + 逐像素乘
|
||||
@@ -1158,26 +1171,28 @@ impl SettingsPane {
|
||||
}
|
||||
self.runtime.background_image_opacity = opacity;
|
||||
cx.notify();
|
||||
self.schedule_slider_persist("background_image_opacity", format!("{opacity:.2}"), cx);
|
||||
self.schedule_slider_persist(
|
||||
vec![("background_image_opacity", format!("{opacity:.2}"))],
|
||||
cx,
|
||||
);
|
||||
}
|
||||
|
||||
/// 滑块停手后才落盘。滑块只有 `Change` 事件(组件库没有"松手"信号),
|
||||
/// 所以用 debounce:每个新事件覆盖上一个待落盘任务,旧 `Task` drop 即取消。
|
||||
/// `Change` 同时覆盖鼠标拖动和键盘调整,因此统一用 debounce 延后落盘:
|
||||
/// 每个新事件覆盖上一个待落盘任务,旧 `Task` drop 即取消。
|
||||
///
|
||||
/// 落盘那一次会走完整的 [`Self::persist`](写盘 + 设置重载 + 每终端
|
||||
/// `apply_settings` + 主题重建 + 壁纸重烘焙),跑一次没问题;每帧跑就是
|
||||
/// 2026-08-21 报的"拖拽卡顿的要死"。
|
||||
fn schedule_slider_persist(
|
||||
&mut self,
|
||||
key: &'static str,
|
||||
value: String,
|
||||
updates: Vec<(&'static str, String)>,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
let executor = cx.background_executor().clone();
|
||||
self.slider_persist = Some(cx.spawn(async move |this, cx| {
|
||||
executor.timer(Duration::from_millis(180)).await;
|
||||
let _ = this.update(cx, |this, cx| {
|
||||
this.persist(&[(key, value)], cx);
|
||||
this.persist(&updates, cx);
|
||||
});
|
||||
}));
|
||||
}
|
||||
|
||||
@@ -209,8 +209,16 @@ impl SettingsPane {
|
||||
.child(
|
||||
gpui::canvas(
|
||||
move |bounds, _, cx| {
|
||||
let _ = picker.update(cx, |picker, _| {
|
||||
let _ = picker.update(cx, |picker, cx| {
|
||||
if picker.font_picker_trigger_bounds == Some(bounds) {
|
||||
return;
|
||||
}
|
||||
picker.font_picker_trigger_bounds = Some(bounds);
|
||||
// anchored() 消费的是上一轮 render 拿到的窗口坐标;
|
||||
// 聚焦自动滚动后补一帧,弹层才会继续贴住输入框。
|
||||
if picker.font_picker_open {
|
||||
cx.notify();
|
||||
}
|
||||
});
|
||||
},
|
||||
|_, _, _, _| {},
|
||||
@@ -292,6 +300,8 @@ impl SettingsPane {
|
||||
.enumerate()
|
||||
.map(|(index, family)| {
|
||||
let family_name: SharedString = family.clone().into();
|
||||
let display_name: SharedString = font_display_name(family).into();
|
||||
let tooltip_name = display_name.clone();
|
||||
h_flex()
|
||||
.id(SharedString::from(format!("font-chain-row-{index}")))
|
||||
.h(px(38.0))
|
||||
@@ -312,12 +322,17 @@ impl SettingsPane {
|
||||
)
|
||||
.child(
|
||||
div()
|
||||
.id(("font-chain-name", index))
|
||||
.flex_1()
|
||||
.min_w_0()
|
||||
.truncate()
|
||||
.text_sm()
|
||||
.font_family(family_name)
|
||||
.child(font_display_name(family)),
|
||||
.tooltip(move |window, cx| {
|
||||
gpui_component::tooltip::Tooltip::new(tooltip_name.clone())
|
||||
.build(window, cx)
|
||||
})
|
||||
.child(display_name),
|
||||
)
|
||||
.when(index == 0, |row| row.child(badge("主", muted)))
|
||||
.child(Self::chain_action(
|
||||
@@ -358,6 +373,8 @@ impl SettingsPane {
|
||||
let select_name = entry.name.clone();
|
||||
let append_name = entry.name.clone();
|
||||
let family: SharedString = entry.name.clone().into();
|
||||
let display_name: SharedString = font_display_name(&entry.name).into();
|
||||
let tooltip_name = display_name.clone();
|
||||
let bundled = entry.source == FontSource::Bundled;
|
||||
let already_selected =
|
||||
families.iter().any(|family| family.eq_ignore_ascii_case(&entry.name));
|
||||
@@ -374,12 +391,17 @@ impl SettingsPane {
|
||||
.hover(|row| row.bg(hover_bg))
|
||||
.child(
|
||||
div()
|
||||
.id(("font-available-name", index))
|
||||
.flex_1()
|
||||
.min_w_0()
|
||||
.truncate()
|
||||
.text_sm()
|
||||
.font_family(family)
|
||||
.child(font_display_name(&entry.name)),
|
||||
.tooltip(move |window, cx| {
|
||||
gpui_component::tooltip::Tooltip::new(tooltip_name.clone())
|
||||
.build(window, cx)
|
||||
})
|
||||
.child(display_name),
|
||||
)
|
||||
.when(bundled, |row| row.child(badge("内置", muted)))
|
||||
.when(entry.source == FontSource::Imported, |row| {
|
||||
|
||||
@@ -371,7 +371,6 @@ impl SettingsPane {
|
||||
// 占位「搜索动作或按键…」,下面再空 12px 才到冲突条 / 分组。
|
||||
v_flex()
|
||||
.w_full()
|
||||
.gap(px(GROUP_GAP))
|
||||
// 捕获态的「点击任何位置先撤销」(旧壳 input/chrome.rs 的统一撤
|
||||
// 销合同):行的 mouse_down 会 stop_propagation 自行处理转移/
|
||||
// 取消,搜索框这里显式撤销(旧壳点搜索框 = blur 捕获),其余
|
||||
|
||||
@@ -33,9 +33,7 @@ use super::session::{self, TerminalSession};
|
||||
use super::suggest;
|
||||
use super::{KEY_CONTEXT, TerminalBackTab, TerminalTab};
|
||||
use crate::gpui_shell::config::Settings;
|
||||
use crate::gpui_shell::prelude::{
|
||||
ActiveTheme as _, Colorize as _, DialogButtonProps, center_confirm_dialog,
|
||||
};
|
||||
use crate::gpui_shell::prelude::{ActiveTheme as _, ButtonVariant, Colorize as _, confirm_dialog};
|
||||
use crate::{config::UiConfig, font_install::REQUIRED_FONT_FAMILY};
|
||||
use futures::StreamExt as _;
|
||||
|
||||
@@ -1398,19 +1396,19 @@ impl TerminalView {
|
||||
window.open_dialog(cx, move |dialog, window, _cx| {
|
||||
let text = text.clone();
|
||||
let view = view.clone();
|
||||
center_confirm_dialog(dialog, window)
|
||||
.title(title.clone())
|
||||
.button_props(
|
||||
DialogButtonProps::default()
|
||||
.ok_text(ok_text.clone())
|
||||
.cancel_text(cancel_text.clone())
|
||||
.show_cancel(true),
|
||||
)
|
||||
.child(body.clone())
|
||||
.on_ok(move |_, _window, cx| {
|
||||
let _ = view.update(cx, |this, cx| this.paste_now(&text, cx));
|
||||
true
|
||||
})
|
||||
confirm_dialog(
|
||||
dialog,
|
||||
window,
|
||||
title.clone(),
|
||||
body.clone(),
|
||||
ok_text.clone(),
|
||||
cancel_text.clone(),
|
||||
ButtonVariant::Primary,
|
||||
)
|
||||
.on_ok(move |_, _window, cx| {
|
||||
let _ = view.update(cx, |this, cx| this.paste_now(&text, cx));
|
||||
true
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -106,6 +106,10 @@ gpui::actions!(
|
||||
/// 不能挂全局:否则 CC/Codex 的终止对话键到不了 PTY。
|
||||
const PALETTE_KEY_CONTEXT: &str = "NebulaCommandPalette";
|
||||
|
||||
/// 侧栏槽位右缘到正文卡可见左缘的 8px 卡缝;拖拽热区必须对准后者。
|
||||
const SIDEBAR_RESIZE_VISUAL_OFFSET: f32 = 8.0;
|
||||
const SIDEBAR_RESIZE_HANDLE_WIDTH: f32 = 6.0;
|
||||
|
||||
/// 工作区静态默认绑定的 combo 集([`init`] 的镜像)。撤销已失效的自定义
|
||||
/// 注入时要排除:gpui 的 NoAction 打在静态默认键上会误杀基础功能。
|
||||
const STATIC_DEFAULT_COMBOS: &[&str] = &[
|
||||
@@ -1571,32 +1575,31 @@ impl NebulaWorkspace {
|
||||
window.open_dialog(cx, move |dialog, window, _cx| {
|
||||
let confirm_workspace = confirm_workspace.clone();
|
||||
let close_workspace = close_workspace.clone();
|
||||
center_confirm_dialog(dialog, window)
|
||||
.title("关闭窗口?")
|
||||
.button_props(
|
||||
DialogButtonProps::default()
|
||||
.ok_text("关闭")
|
||||
.ok_variant(gpui_component::button::ButtonVariant::Danger)
|
||||
.cancel_text("取消")
|
||||
.show_cancel(true),
|
||||
)
|
||||
.child(body.clone())
|
||||
.on_ok(move |_, window, cx| {
|
||||
let _ = confirm_workspace.update(cx, |workspace, cx| {
|
||||
workspace.save_clean_window_session(cx);
|
||||
workspace.window_close_confirm_open = false;
|
||||
// `remove_window` 是确认后的最终动作,不会重新触发
|
||||
// should-close,从而避免再次弹出同一确认框。
|
||||
window.remove_window();
|
||||
});
|
||||
true
|
||||
})
|
||||
.on_close(move |_, _, cx| {
|
||||
let _ = close_workspace.update(cx, |workspace, cx| {
|
||||
workspace.window_close_confirm_open = false;
|
||||
cx.notify();
|
||||
});
|
||||
})
|
||||
confirm_dialog(
|
||||
dialog,
|
||||
window,
|
||||
"关闭窗口?",
|
||||
body.clone(),
|
||||
"关闭",
|
||||
"取消",
|
||||
ButtonVariant::Danger,
|
||||
)
|
||||
.on_ok(move |_, window, cx| {
|
||||
let _ = confirm_workspace.update(cx, |workspace, cx| {
|
||||
workspace.save_clean_window_session(cx);
|
||||
workspace.window_close_confirm_open = false;
|
||||
// `remove_window` 是确认后的最终动作,不会重新触发
|
||||
// should-close,从而避免再次弹出同一确认框。
|
||||
window.remove_window();
|
||||
});
|
||||
true
|
||||
})
|
||||
.on_close(move |_, _, cx| {
|
||||
let _ = close_workspace.update(cx, |workspace, cx| {
|
||||
workspace.window_close_confirm_open = false;
|
||||
cx.notify();
|
||||
});
|
||||
})
|
||||
});
|
||||
false
|
||||
}
|
||||
@@ -1616,22 +1619,21 @@ impl NebulaWorkspace {
|
||||
let workspace = cx.entity().downgrade();
|
||||
window.open_dialog(cx, move |dialog, window, _cx| {
|
||||
let workspace = workspace.clone();
|
||||
center_confirm_dialog(dialog, window)
|
||||
.title("关闭此分栏?")
|
||||
.button_props(
|
||||
DialogButtonProps::default()
|
||||
.ok_text("关闭")
|
||||
.ok_variant(gpui_component::button::ButtonVariant::Danger)
|
||||
.cancel_text("取消")
|
||||
.show_cancel(true),
|
||||
)
|
||||
.child(body.clone())
|
||||
.on_ok(move |_, window, cx| {
|
||||
let _ = workspace.update(cx, |workspace, cx| {
|
||||
workspace.close_pane(tab_ix, pane_id, window, cx);
|
||||
});
|
||||
true
|
||||
})
|
||||
confirm_dialog(
|
||||
dialog,
|
||||
window,
|
||||
"关闭此分栏?",
|
||||
body.clone(),
|
||||
"关闭",
|
||||
"取消",
|
||||
ButtonVariant::Danger,
|
||||
)
|
||||
.on_ok(move |_, window, cx| {
|
||||
let _ = workspace.update(cx, |workspace, cx| {
|
||||
workspace.close_pane(tab_ix, pane_id, window, cx);
|
||||
});
|
||||
true
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1644,22 +1646,21 @@ impl NebulaWorkspace {
|
||||
let workspace = cx.entity().downgrade();
|
||||
window.open_dialog(cx, move |dialog, window, _cx| {
|
||||
let workspace = workspace.clone();
|
||||
center_confirm_dialog(dialog, window)
|
||||
.title("关闭此标签页?")
|
||||
.button_props(
|
||||
DialogButtonProps::default()
|
||||
.ok_text("关闭")
|
||||
.ok_variant(gpui_component::button::ButtonVariant::Danger)
|
||||
.cancel_text("取消")
|
||||
.show_cancel(true),
|
||||
)
|
||||
.child(body.clone())
|
||||
.on_ok(move |_, window, cx| {
|
||||
let _ = workspace.update(cx, |workspace, cx| {
|
||||
workspace.close_tab(tab_ix, window, cx);
|
||||
});
|
||||
true
|
||||
})
|
||||
confirm_dialog(
|
||||
dialog,
|
||||
window,
|
||||
"关闭此标签页?",
|
||||
body.clone(),
|
||||
"关闭",
|
||||
"取消",
|
||||
ButtonVariant::Danger,
|
||||
)
|
||||
.on_ok(move |_, window, cx| {
|
||||
let _ = workspace.update(cx, |workspace, cx| {
|
||||
workspace.close_tab(tab_ix, window, cx);
|
||||
});
|
||||
true
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
@@ -4508,8 +4509,11 @@ impl Render for NebulaWorkspace {
|
||||
.absolute()
|
||||
.top_0()
|
||||
.bottom_0()
|
||||
.left(px(-3.0))
|
||||
.w(px(6.0))
|
||||
.left(px(
|
||||
SIDEBAR_RESIZE_VISUAL_OFFSET
|
||||
- SIDEBAR_RESIZE_HANDLE_WIDTH * 0.5,
|
||||
))
|
||||
.w(px(SIDEBAR_RESIZE_HANDLE_WIDTH))
|
||||
.cursor_col_resize()
|
||||
.on_mouse_down(
|
||||
MouseButton::Left,
|
||||
@@ -4621,10 +4625,12 @@ impl Render for NebulaWorkspace {
|
||||
.occlude()
|
||||
.cursor_col_resize()
|
||||
.on_mouse_move(cx.listener(|this, event: &gpui::MouseMoveEvent, _, cx| {
|
||||
let width = f32::from(event.position.x).clamp(
|
||||
nebula_settings::MIN_SIDEBAR_WIDTH,
|
||||
nebula_settings::MAX_SIDEBAR_WIDTH,
|
||||
);
|
||||
let width = (f32::from(event.position.x)
|
||||
- SIDEBAR_RESIZE_VISUAL_OFFSET)
|
||||
.clamp(
|
||||
nebula_settings::MIN_SIDEBAR_WIDTH,
|
||||
nebula_settings::MAX_SIDEBAR_WIDTH,
|
||||
);
|
||||
if (width - this.sidebar_width).abs() >= 0.5 {
|
||||
this.sidebar_width = width;
|
||||
cx.notify();
|
||||
|
||||
@@ -417,29 +417,28 @@ impl NebulaWorkspace {
|
||||
window.open_dialog(cx, move |dialog, window, _cx| {
|
||||
let workspace = workspace.clone();
|
||||
let path = path.clone();
|
||||
center_confirm_dialog(dialog, window)
|
||||
.title(title.clone())
|
||||
.button_props(
|
||||
DialogButtonProps::default()
|
||||
.ok_text("删除")
|
||||
.ok_variant(gpui_component::button::ButtonVariant::Danger)
|
||||
.cancel_text("取消")
|
||||
.show_cancel(true),
|
||||
)
|
||||
.child(body.clone())
|
||||
.on_ok(move |_, _, cx| {
|
||||
let _ = workspace.update(cx, |this, cx| {
|
||||
match crate::display::send_to_recycle_bin(&path) {
|
||||
Ok(()) => {
|
||||
this.side_panel.request_refresh();
|
||||
this.sync_side_panel_to_active(false, cx);
|
||||
},
|
||||
Err(error) => this.side_panel.set_notice(format!("删除失败:{error}")),
|
||||
}
|
||||
cx.notify();
|
||||
});
|
||||
true
|
||||
})
|
||||
confirm_dialog(
|
||||
dialog,
|
||||
window,
|
||||
title.clone(),
|
||||
body.clone(),
|
||||
"删除",
|
||||
"取消",
|
||||
ButtonVariant::Danger,
|
||||
)
|
||||
.on_ok(move |_, _, cx| {
|
||||
let _ = workspace.update(cx, |this, cx| {
|
||||
match crate::display::send_to_recycle_bin(&path) {
|
||||
Ok(()) => {
|
||||
this.side_panel.request_refresh();
|
||||
this.sync_side_panel_to_active(false, cx);
|
||||
},
|
||||
Err(error) => this.side_panel.set_notice(format!("删除失败:{error}")),
|
||||
}
|
||||
cx.notify();
|
||||
});
|
||||
true
|
||||
})
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,6 +32,9 @@ pub(crate) fn show_update_notification(
|
||||
let action_result = result.clone();
|
||||
let notification = Notification::warning(message)
|
||||
.title(title)
|
||||
// 更新是需要用户明确处理的待办;保持到查看或手动关闭,避免短暂出现后
|
||||
// 被误判为“没有右下角提示”。
|
||||
.autohide(false)
|
||||
.w_auto()
|
||||
.min_w(px(300.0))
|
||||
.max_w(px(440.0))
|
||||
@@ -129,10 +132,12 @@ pub(crate) fn open_update_dialog(
|
||||
),
|
||||
)
|
||||
.child(
|
||||
div().text_sm().line_height(relative(1.55)).text_color(muted).child(hint.clone()),
|
||||
div().text_base().line_height(relative(1.5)).text_color(muted).child(hint.clone()),
|
||||
);
|
||||
center_confirm_dialog(dialog, window)
|
||||
.title(title.clone())
|
||||
center_modal_dialog(dialog, window, 250.0)
|
||||
.close_button(false)
|
||||
.overlay_closable(false)
|
||||
.title(div().text_lg().font_semibold().line_height(relative(1.0)).child(title.clone()))
|
||||
.footer(
|
||||
DialogFooter::new()
|
||||
.child(
|
||||
|
||||
@@ -118,6 +118,21 @@ pub fn spawn_gpui_once(sender: std::sync::mpsc::Sender<crate::gpui_shell::GpuiSh
|
||||
return;
|
||||
}
|
||||
let spawned = std::thread::Builder::new().name("update-check-gpui".into()).spawn(move || {
|
||||
if cfg!(debug_assertions) {
|
||||
// 调试包启动后强制走一次完整的“事件 -> 右下角通知 -> 详情弹窗”链路。
|
||||
// STARTED 仍保证每进程仅一次;正式包的该条件恒为 false,不会触发。
|
||||
std::thread::sleep(Duration::from_secs(2));
|
||||
let current = env!("CARGO_PKG_VERSION").to_owned();
|
||||
let result = UpdateCheckResult {
|
||||
latest: format!("{current}-test"),
|
||||
current,
|
||||
update_available: true,
|
||||
};
|
||||
log::info!("update-check: forcing one GPUI update reminder in debug build");
|
||||
let _ = sender.send(crate::gpui_shell::GpuiShellEvent::UpdateAvailable(result));
|
||||
return;
|
||||
}
|
||||
|
||||
// 对齐旧壳:首屏和首个终端会话稳定后再联网。
|
||||
std::thread::sleep(Duration::from_secs(12));
|
||||
let result = match check_now() {
|
||||
|
||||
Reference in new issue
Block a user