mirror of
https://github.com/rust-kotlin/ashell.git
synced 2026-09-22 00:00:59 +00:00
feat: settings dialog using Settings component #19
This commit is contained in:
+11
-3
@@ -67,7 +67,6 @@ terminal_font_size: "Terminal Font Size"
|
||||
system_default: "System Default"
|
||||
ui_font_family: "UI Font"
|
||||
terminal_font_family: "Terminal Font"
|
||||
theme_management_hint: "Theme mode and light/dark theme families are managed from the top-bar theme button."
|
||||
monitoring_position: "Monitoring Position"
|
||||
position_bottom: "Bottom"
|
||||
position_sidebar: "Sidebar"
|
||||
@@ -95,7 +94,7 @@ n_folders: "%{folders} folders"
|
||||
n_files_and_folders: "%{files} files and %{folders} folders"
|
||||
reset: "Reset"
|
||||
reset_layout: "Reset Default Layout"
|
||||
reset_layout_success: "Layout reset. Window size takes effect on restart."
|
||||
reset_layout_hint: "Reset panel layout proportion. Window size will take effect on next startup."
|
||||
|
||||
transfers: "Transfers"
|
||||
no_transfers_yet: "No transfers yet"
|
||||
@@ -109,9 +108,18 @@ session: "Session"
|
||||
software_builtin: "Built-in"
|
||||
retry: "Retry"
|
||||
right_click_copy_paste: "Right-click Copy/Paste"
|
||||
copy_paste_hint: "Tip: Regardless of this switch, you can always use %{key} + C/V to copy/paste."
|
||||
copy_paste_hint: "Regardless of this switch, you can always use %{key} + C/V to copy/paste."
|
||||
edit_file: "Edit File"
|
||||
edit_file_tooltip: "Download to temporary directory, open in default editor, and auto upload on save."
|
||||
auto_saved_and_uploaded: "Auto-saved & uploaded: %{base}"
|
||||
scroll: "Scroll ↓"
|
||||
clone: "Clone"
|
||||
settings_general: "General"
|
||||
settings_key_bindings: "Key Bindings"
|
||||
settings_help: "Help"
|
||||
settings_about: "About"
|
||||
settings_group_appearance: "Appearance"
|
||||
settings_group_font: "Font"
|
||||
settings_group_other: "Other"
|
||||
theme: "Theme"
|
||||
theme_mode: "Theme Mode"
|
||||
|
||||
+12
-3
@@ -69,7 +69,6 @@ terminal_font_size: "终端字体大小"
|
||||
system_default: "系统默认"
|
||||
ui_font_family: "界面字体"
|
||||
terminal_font_family: "终端字体"
|
||||
theme_management_hint: "深色/浅色模式以及主题配色由顶部栏的样式按钮统一管理。"
|
||||
monitoring_position: "系统监控显示位置"
|
||||
position_bottom: "底部"
|
||||
position_sidebar: "侧边栏"
|
||||
@@ -97,7 +96,7 @@ n_folders: "%{folders} 个文件夹"
|
||||
n_files_and_folders: "%{files} 个文件及 %{folders} 个文件夹"
|
||||
reset: "重置"
|
||||
reset_layout: "恢复默认布局"
|
||||
reset_layout_success: "布局占比已恢复默认。窗口大小将在下次启动时生效。"
|
||||
reset_layout_hint: "重置面板布局占比,窗口大小将在下次启动时生效"
|
||||
|
||||
transfers: "传输记录"
|
||||
no_transfers_yet: "暂无传输记录"
|
||||
@@ -111,9 +110,19 @@ session: "会话"
|
||||
software_builtin: "软件内置"
|
||||
retry: "重试"
|
||||
right_click_copy_paste: "右键复制/粘贴"
|
||||
copy_paste_hint: "提示:不管是否开启此开关,都可以使用 %{key} + C/V 快捷键进行复制和粘贴。"
|
||||
copy_paste_hint: "不管是否开启此开关,都可以使用 %{key} + C/V 快捷键进行复制和粘贴。"
|
||||
edit_file: "编辑文件"
|
||||
edit_file_tooltip: "下载到临时目录,使用默认编辑器打开,并在保存后自动上传。"
|
||||
auto_saved_and_uploaded: "已自动保存并上传: %{base}"
|
||||
scroll: "滑动查看 ↓"
|
||||
clone: "克隆"
|
||||
about_feedback_hint: "如果遇到问题或者有什么建议,请前往我们的 GitHub 项目中反馈。"
|
||||
settings_general: "常规"
|
||||
settings_key_bindings: "快捷键"
|
||||
settings_help: "帮助"
|
||||
settings_about: "关于"
|
||||
settings_group_appearance: "外观"
|
||||
settings_group_font: "字体"
|
||||
settings_group_other: "其它"
|
||||
theme: "主题"
|
||||
theme_mode: "主题模式"
|
||||
|
||||
+480
-447
@@ -867,465 +867,498 @@ impl Ashell {
|
||||
window.open_dialog(cx, move |dialog: Dialog, _window, _| {
|
||||
dialog
|
||||
.title(t!("settings").to_string())
|
||||
.w(px(560.))
|
||||
.w(px(840.))
|
||||
.h(px(560.))
|
||||
.content({
|
||||
let view = view.clone();
|
||||
move |content, window, cx| {
|
||||
move |content, _window, _cx| {
|
||||
use gpui_component::setting::{Settings, SettingPage, SettingGroup, SettingItem, SettingField};
|
||||
use gpui::IntoElement;
|
||||
let version = env!("CARGO_PKG_VERSION");
|
||||
let view_clone_for_general = view.clone();
|
||||
|
||||
content.child(
|
||||
v_flex()
|
||||
.gap_3()
|
||||
.child(
|
||||
h_flex()
|
||||
.items_center()
|
||||
.gap_3()
|
||||
.child(
|
||||
div()
|
||||
.w(px(240.))
|
||||
.child(t!("ui_font_size").to_string()),
|
||||
)
|
||||
.child(
|
||||
Button::new("ui-font-size-down")
|
||||
.label("-")
|
||||
.on_click(window.listener_for(
|
||||
&view,
|
||||
|this, _, _, cx| {
|
||||
this.change_ui_font_size(-1.0, cx)
|
||||
},
|
||||
)),
|
||||
)
|
||||
.child(div().min_w(px(64.)).text_center().child(format!(
|
||||
"{:.0}px",
|
||||
view.read(cx).ui_font_size
|
||||
)))
|
||||
.child(
|
||||
Button::new("ui-font-size-up")
|
||||
.label("+")
|
||||
.on_click(window.listener_for(
|
||||
&view,
|
||||
|this, _, _, cx| {
|
||||
this.change_ui_font_size(1.0, cx)
|
||||
},
|
||||
)),
|
||||
),
|
||||
)
|
||||
.child(
|
||||
h_flex()
|
||||
.items_center()
|
||||
.gap_3()
|
||||
.child(
|
||||
div()
|
||||
.w(px(240.))
|
||||
.child(t!("terminal_font_size").to_string()),
|
||||
)
|
||||
.child(
|
||||
Button::new("font-size-down")
|
||||
.label("-")
|
||||
.on_click(window.listener_for(
|
||||
&view,
|
||||
|this, _, _, cx| {
|
||||
this.change_terminal_font_size(-1.0, cx)
|
||||
},
|
||||
)),
|
||||
)
|
||||
.child(div().min_w(px(64.)).text_center().child(format!(
|
||||
"{:.0}px",
|
||||
view.read(cx).terminal_font_size
|
||||
)))
|
||||
.child(
|
||||
Button::new("font-size-up")
|
||||
.label("+")
|
||||
.on_click(window.listener_for(
|
||||
&view,
|
||||
|this, _, _, cx| {
|
||||
this.change_terminal_font_size(1.0, cx)
|
||||
},
|
||||
)),
|
||||
),
|
||||
)
|
||||
.child(
|
||||
h_flex()
|
||||
.items_center()
|
||||
.gap_3()
|
||||
.child(
|
||||
div()
|
||||
.w(px(240.))
|
||||
.child(t!("ui_font_family").to_string()),
|
||||
)
|
||||
.child(
|
||||
Button::new("ui-font-dropdown")
|
||||
.small()
|
||||
.icon(IconName::ChevronsUpDown)
|
||||
.label({
|
||||
let current = view
|
||||
.read(cx)
|
||||
.ui_font_family
|
||||
.to_string();
|
||||
let names = cx.text_system().all_font_names();
|
||||
if current == *".SystemUIFont"
|
||||
|| current.is_empty()
|
||||
|| !names.contains(¤t)
|
||||
{
|
||||
t!("system_default").to_string()
|
||||
} else if current == "Maple Mono NF CN" {
|
||||
format!("Maple Mono NF CN ({})", t!("software_builtin"))
|
||||
} else {
|
||||
current
|
||||
}
|
||||
})
|
||||
.dropdown_menu_with_anchor(
|
||||
Anchor::BottomRight,
|
||||
{
|
||||
let view = view.clone();
|
||||
move |mut menu, window, cx| {
|
||||
let current = view
|
||||
.read(cx)
|
||||
.ui_font_family
|
||||
.to_string();
|
||||
let mut names =
|
||||
cx.text_system().all_font_names();
|
||||
menu = menu.min_w(200.).max_h(
|
||||
px(320.),
|
||||
).scrollable(true);
|
||||
// "System Default" entry
|
||||
menu = menu.item(
|
||||
PopupMenuItem::new(
|
||||
t!(
|
||||
"system_default"
|
||||
)
|
||||
.to_string(),
|
||||
)
|
||||
.checked(
|
||||
current == *".SystemUIFont"
|
||||
|| current
|
||||
.is_empty(),
|
||||
)
|
||||
.on_click(window
|
||||
.listener_for(
|
||||
&view,
|
||||
move |this, _, window,
|
||||
cx| {
|
||||
this.change_ui_font_family(
|
||||
".SystemUIFont",
|
||||
window,
|
||||
cx,
|
||||
);
|
||||
},
|
||||
)),
|
||||
);
|
||||
|
||||
let maple_font = "Maple Mono NF CN".to_string();
|
||||
if names.contains(&maple_font) {
|
||||
names.retain(|n| n != &maple_font);
|
||||
menu = menu.item(
|
||||
PopupMenuItem::new(format!("{} ({})", maple_font, t!("software_builtin")))
|
||||
.checked(current == maple_font)
|
||||
.on_click(window.listener_for(&view, move |this, _, window, cx| {
|
||||
this.change_ui_font_family("Maple Mono NF CN", window, cx);
|
||||
}))
|
||||
).separator();
|
||||
Settings::new("settings")
|
||||
.sidebar_width(px(180.))
|
||||
.page(
|
||||
SettingPage::new(t!("settings_general").to_string())
|
||||
.icon(IconName::Settings)
|
||||
.default_open(true)
|
||||
.group(
|
||||
SettingGroup::new()
|
||||
.title(t!("settings_group_appearance").to_string())
|
||||
.item(
|
||||
SettingItem::new(
|
||||
t!("theme_mode").to_string(),
|
||||
SettingField::render({
|
||||
let view = view_clone_for_general.clone();
|
||||
move |_, _window, cx| {
|
||||
let (follow_system, is_dark_mode) = {
|
||||
let state = view.read(cx);
|
||||
(state.follow_system_theme, state.theme_mode.is_dark())
|
||||
};
|
||||
Button::new("theme-mode-dropdown")
|
||||
.small()
|
||||
.icon(if follow_system { IconName::Sun } else if is_dark_mode { IconName::Moon } else { IconName::Sun })
|
||||
.label(if follow_system { t!("follow_system").to_string() } else if is_dark_mode { t!("use_dark_mode").to_string() } else { t!("use_light_mode").to_string() })
|
||||
.dropdown_menu_with_anchor(Anchor::BottomRight, {
|
||||
let view = view.clone();
|
||||
move |mut menu, window, cx| {
|
||||
let (follow_system, is_dark_mode) = {
|
||||
let state = view.read(cx);
|
||||
(state.follow_system_theme, state.theme_mode.is_dark())
|
||||
};
|
||||
menu = menu.min_w(160.)
|
||||
.item(
|
||||
PopupMenuItem::new(t!("follow_system").to_string())
|
||||
.checked(follow_system)
|
||||
.on_click(window.listener_for(&view, |this, _, window, cx| {
|
||||
this.set_follow_system_theme(true, window, cx)
|
||||
}))
|
||||
)
|
||||
.item(
|
||||
PopupMenuItem::new(t!("use_light_mode").to_string())
|
||||
.checked(!follow_system && !is_dark_mode)
|
||||
.on_click(window.listener_for(&view, |this, _, window, cx| {
|
||||
this.switch_theme_mode(crate::app::ThemeMode::Light, window, cx)
|
||||
}))
|
||||
)
|
||||
.item(
|
||||
PopupMenuItem::new(t!("use_dark_mode").to_string())
|
||||
.checked(!follow_system && is_dark_mode)
|
||||
.on_click(window.listener_for(&view, |this, _, window, cx| {
|
||||
this.switch_theme_mode(crate::app::ThemeMode::Dark, window, cx)
|
||||
}))
|
||||
);
|
||||
menu
|
||||
}
|
||||
})
|
||||
.into_any_element()
|
||||
}
|
||||
|
||||
for name in names {
|
||||
let checked =
|
||||
name == current;
|
||||
menu = menu.item(
|
||||
PopupMenuItem::new(
|
||||
name.clone(),
|
||||
)
|
||||
.checked(checked)
|
||||
.on_click(window
|
||||
.listener_for(
|
||||
&view,
|
||||
move |this, _,
|
||||
window, cx| {
|
||||
this
|
||||
.change_ui_font_family(
|
||||
&name,
|
||||
window,
|
||||
cx,
|
||||
);
|
||||
},
|
||||
)),
|
||||
);
|
||||
})
|
||||
)
|
||||
)
|
||||
.item(
|
||||
SettingItem::new(
|
||||
t!("light_theme").to_string(),
|
||||
SettingField::render({
|
||||
let view = view_clone_for_general.clone();
|
||||
move |_, _window, cx| {
|
||||
let current_theme = view.read(cx).light_theme_name.to_string();
|
||||
Button::new("light-theme-dropdown")
|
||||
.small()
|
||||
.icon(IconName::Sun)
|
||||
.label(current_theme.clone())
|
||||
.dropdown_menu_with_anchor(Anchor::BottomRight, {
|
||||
let view = view.clone();
|
||||
move |mut menu, window, cx| {
|
||||
let current_theme = view.read(cx).light_theme_name.to_string();
|
||||
let themes = gpui_component::ThemeRegistry::global(cx).sorted_themes();
|
||||
let light_themes: Vec<_> = themes.into_iter().filter(|t| !t.mode.is_dark()).map(|t| t.name.clone()).collect();
|
||||
menu = menu.min_w(160.).max_h(px(320.)).scrollable(true);
|
||||
for theme_name in light_themes {
|
||||
let checked = theme_name == current_theme;
|
||||
menu = menu.item(
|
||||
PopupMenuItem::new(theme_name.clone())
|
||||
.checked(checked)
|
||||
.on_click(window.listener_for(&view, move |this, _, window, cx| {
|
||||
this.apply_theme(theme_name.clone(), window, cx)
|
||||
}))
|
||||
);
|
||||
}
|
||||
menu
|
||||
}
|
||||
})
|
||||
.into_any_element()
|
||||
}
|
||||
menu
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
)
|
||||
.child(
|
||||
h_flex()
|
||||
.items_center()
|
||||
.gap_3()
|
||||
.child(
|
||||
div()
|
||||
.w(px(240.))
|
||||
.child(t!("terminal_font_family").to_string()),
|
||||
)
|
||||
.child(
|
||||
Button::new("terminal-font-dropdown")
|
||||
.small()
|
||||
.icon(IconName::ChevronsUpDown)
|
||||
.label({
|
||||
let current = view.read(cx).terminal_font_family.to_string();
|
||||
if current == "Maple Mono NF CN" {
|
||||
format!("Maple Mono NF CN ({})", t!("software_builtin"))
|
||||
} else {
|
||||
current
|
||||
}
|
||||
})
|
||||
.dropdown_menu_with_anchor(
|
||||
Anchor::BottomRight,
|
||||
{
|
||||
let view = view.clone();
|
||||
move |mut menu, window, cx| {
|
||||
let current = view
|
||||
.read(cx)
|
||||
.terminal_font_family
|
||||
.to_string();
|
||||
let mut names =
|
||||
cx.text_system().all_font_names();
|
||||
menu = menu.min_w(200.).max_h(
|
||||
px(320.),
|
||||
).scrollable(true);
|
||||
|
||||
let maple_font = "Maple Mono NF CN".to_string();
|
||||
if names.contains(&maple_font) {
|
||||
names.retain(|n| n != &maple_font);
|
||||
menu = menu.item(
|
||||
PopupMenuItem::new(format!("{} ({})", maple_font, t!("software_builtin")))
|
||||
.checked(current == maple_font)
|
||||
.on_click(window.listener_for(&view, move |this, _, _window, cx| {
|
||||
this.change_terminal_font_family("Maple Mono NF CN", cx);
|
||||
}))
|
||||
).separator();
|
||||
})
|
||||
)
|
||||
)
|
||||
.item(
|
||||
SettingItem::new(
|
||||
t!("dark_theme").to_string(),
|
||||
SettingField::render({
|
||||
let view = view_clone_for_general.clone();
|
||||
move |_, _window, cx| {
|
||||
let current_theme = view.read(cx).dark_theme_name.to_string();
|
||||
Button::new("dark-theme-dropdown")
|
||||
.small()
|
||||
.icon(IconName::Moon)
|
||||
.label(current_theme.clone())
|
||||
.dropdown_menu_with_anchor(Anchor::BottomRight, {
|
||||
let view = view.clone();
|
||||
move |mut menu, window, cx| {
|
||||
let current_theme = view.read(cx).dark_theme_name.to_string();
|
||||
let themes = gpui_component::ThemeRegistry::global(cx).sorted_themes();
|
||||
let dark_themes: Vec<_> = themes.into_iter().filter(|t| t.mode.is_dark()).map(|t| t.name.clone()).collect();
|
||||
menu = menu.min_w(160.).max_h(px(320.)).scrollable(true);
|
||||
for theme_name in dark_themes {
|
||||
let checked = theme_name == current_theme;
|
||||
menu = menu.item(
|
||||
PopupMenuItem::new(theme_name.clone())
|
||||
.checked(checked)
|
||||
.on_click(window.listener_for(&view, move |this, _, window, cx| {
|
||||
this.apply_theme(theme_name.clone(), window, cx)
|
||||
}))
|
||||
);
|
||||
}
|
||||
menu
|
||||
}
|
||||
})
|
||||
.into_any_element()
|
||||
}
|
||||
|
||||
for name in names {
|
||||
let checked =
|
||||
name == current;
|
||||
menu = menu.item(
|
||||
PopupMenuItem::new(
|
||||
name.clone(),
|
||||
)
|
||||
.checked(checked)
|
||||
.on_click(window
|
||||
.listener_for(
|
||||
&view,
|
||||
move |this, _,
|
||||
_window, cx| {
|
||||
this
|
||||
.change_terminal_font_family(
|
||||
&name,
|
||||
cx,
|
||||
);
|
||||
},
|
||||
)),
|
||||
);
|
||||
}
|
||||
menu
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
)
|
||||
.child(
|
||||
v_flex()
|
||||
.gap_1()
|
||||
.child(
|
||||
h_flex()
|
||||
.items_center()
|
||||
.gap_3()
|
||||
.child(div().w(px(240.)).child(t!("right_click_copy_paste").to_string()))
|
||||
.child(
|
||||
Switch::new("right-click-copy-paste")
|
||||
.checked(view.read(cx).config.right_click_copy_paste())
|
||||
.on_click(window.listener_for(
|
||||
&view,
|
||||
|this, checked, _, cx| {
|
||||
this.config.set_right_click_copy_paste(*checked);
|
||||
let _ = this.config.save();
|
||||
cx.notify();
|
||||
},
|
||||
)),
|
||||
})
|
||||
)
|
||||
)
|
||||
)
|
||||
.child(
|
||||
div()
|
||||
.text_size(rems(0.85))
|
||||
.text_color(cx.theme().muted_foreground)
|
||||
.child(t!("copy_paste_hint", key = if cfg!(target_os = "macos") { "Command" } else { "Ctrl" }).to_string())
|
||||
.group(
|
||||
SettingGroup::new()
|
||||
.title(t!("settings_group_font").to_string())
|
||||
.item(
|
||||
SettingItem::new(
|
||||
t!("ui_font_size").to_string(),
|
||||
SettingField::render({
|
||||
let view = view_clone_for_general.clone();
|
||||
move |_, window, cx| {
|
||||
h_flex()
|
||||
.items_center()
|
||||
.gap_3()
|
||||
.child(Button::new("ui-font-size-down").small().label("-").on_click(window.listener_for(&view, |this, _, _, cx| this.change_ui_font_size(-1.0, cx))))
|
||||
.child(div().min_w(px(64.)).text_center().child(format!("{:.0}px", view.read(cx).ui_font_size)))
|
||||
.child(Button::new("ui-font-size-up").small().label("+").on_click(window.listener_for(&view, |this, _, _, cx| this.change_ui_font_size(1.0, cx))))
|
||||
.into_any_element()
|
||||
}
|
||||
})
|
||||
)
|
||||
)
|
||||
.item(
|
||||
SettingItem::new(
|
||||
t!("terminal_font_size").to_string(),
|
||||
SettingField::render({
|
||||
let view = view_clone_for_general.clone();
|
||||
move |_, window, cx| {
|
||||
h_flex()
|
||||
.items_center()
|
||||
.gap_3()
|
||||
.child(Button::new("terminal-font-size-down").small().label("-").on_click(window.listener_for(&view, |this, _, _, cx| this.change_terminal_font_size(-1.0, cx))))
|
||||
.child(div().min_w(px(64.)).text_center().child(format!("{:.0}px", view.read(cx).terminal_font_size)))
|
||||
.child(Button::new("terminal-font-size-up").small().label("+").on_click(window.listener_for(&view, |this, _, _, cx| this.change_terminal_font_size(1.0, cx))))
|
||||
.into_any_element()
|
||||
}
|
||||
})
|
||||
)
|
||||
)
|
||||
.item(
|
||||
SettingItem::new(
|
||||
t!("ui_font_family").to_string(),
|
||||
SettingField::render({
|
||||
let view = view_clone_for_general.clone();
|
||||
move |_, _window, cx| {
|
||||
Button::new("ui-font-dropdown")
|
||||
.small()
|
||||
.icon(IconName::ChevronsUpDown)
|
||||
.label({
|
||||
let current = view.read(cx).ui_font_family.to_string();
|
||||
let names = cx.text_system().all_font_names();
|
||||
if current == *".SystemUIFont" || current.is_empty() || !names.contains(¤t) {
|
||||
t!("system_default").to_string()
|
||||
} else if current == "Maple Mono NF CN" {
|
||||
format!("Maple Mono NF CN ({})", t!("software_builtin"))
|
||||
} else {
|
||||
current
|
||||
}
|
||||
})
|
||||
.dropdown_menu_with_anchor(Anchor::BottomRight, {
|
||||
let view = view.clone();
|
||||
move |mut menu, window, cx| {
|
||||
let current = view.read(cx).ui_font_family.to_string();
|
||||
let mut names = cx.text_system().all_font_names();
|
||||
menu = menu.min_w(200.).max_h(px(320.)).scrollable(true);
|
||||
menu = menu.item(
|
||||
PopupMenuItem::new(t!("system_default").to_string())
|
||||
.checked(current == *".SystemUIFont" || current.is_empty())
|
||||
.on_click(window.listener_for(&view, move |this, _, window, cx| {
|
||||
this.change_ui_font_family(".SystemUIFont", window, cx);
|
||||
}))
|
||||
);
|
||||
let maple_font = "Maple Mono NF CN".to_string();
|
||||
if names.contains(&maple_font) {
|
||||
names.retain(|n| n != &maple_font);
|
||||
menu = menu.item(
|
||||
PopupMenuItem::new(format!("{} ({})", maple_font, t!("software_builtin")))
|
||||
.checked(current == maple_font)
|
||||
.on_click(window.listener_for(&view, move |this, _, window, cx| {
|
||||
this.change_ui_font_family("Maple Mono NF CN", window, cx);
|
||||
}))
|
||||
).separator();
|
||||
}
|
||||
for name in names {
|
||||
let checked = name == current;
|
||||
menu = menu.item(
|
||||
PopupMenuItem::new(name.clone())
|
||||
.checked(checked)
|
||||
.on_click(window.listener_for(&view, move |this, _, window, cx| {
|
||||
this.change_ui_font_family(&name, window, cx);
|
||||
}))
|
||||
);
|
||||
}
|
||||
menu
|
||||
}
|
||||
})
|
||||
.into_any_element()
|
||||
}
|
||||
})
|
||||
)
|
||||
)
|
||||
.item(
|
||||
SettingItem::new(
|
||||
t!("terminal_font_family").to_string(),
|
||||
SettingField::render({
|
||||
let view = view_clone_for_general.clone();
|
||||
move |_, _window, cx| {
|
||||
Button::new("terminal-font-dropdown")
|
||||
.small()
|
||||
.icon(IconName::ChevronsUpDown)
|
||||
.label({
|
||||
let current = view.read(cx).terminal_font_family.to_string();
|
||||
if current == "Maple Mono NF CN" {
|
||||
format!("Maple Mono NF CN ({})", t!("software_builtin"))
|
||||
} else {
|
||||
current
|
||||
}
|
||||
})
|
||||
.dropdown_menu_with_anchor(Anchor::BottomRight, {
|
||||
let view = view.clone();
|
||||
move |mut menu, window, cx| {
|
||||
let current = view.read(cx).terminal_font_family.to_string();
|
||||
let mut names = cx.text_system().all_font_names();
|
||||
menu = menu.min_w(200.).max_h(px(320.)).scrollable(true);
|
||||
let maple_font = "Maple Mono NF CN".to_string();
|
||||
if names.contains(&maple_font) {
|
||||
names.retain(|n| n != &maple_font);
|
||||
menu = menu.item(
|
||||
PopupMenuItem::new(format!("{} ({})", maple_font, t!("software_builtin")))
|
||||
.checked(current == maple_font)
|
||||
.on_click(window.listener_for(&view, move |this, _, _window, cx| {
|
||||
this.change_terminal_font_family("Maple Mono NF CN", cx);
|
||||
}))
|
||||
).separator();
|
||||
}
|
||||
for name in names {
|
||||
let checked = name == current;
|
||||
menu = menu.item(
|
||||
PopupMenuItem::new(name.clone())
|
||||
.checked(checked)
|
||||
.on_click(window.listener_for(&view, move |this, _, _window, cx| {
|
||||
this.change_terminal_font_family(&name, cx);
|
||||
}))
|
||||
);
|
||||
}
|
||||
menu
|
||||
}
|
||||
})
|
||||
.into_any_element()
|
||||
}
|
||||
})
|
||||
)
|
||||
)
|
||||
)
|
||||
.group(
|
||||
SettingGroup::new()
|
||||
.title(t!("settings_group_other").to_string())
|
||||
.item(
|
||||
SettingItem::new(
|
||||
t!("right_click_copy_paste").to_string(),
|
||||
SettingField::render({
|
||||
let view = view_clone_for_general.clone();
|
||||
move |_, window, cx| {
|
||||
Switch::new("right-click-copy-paste")
|
||||
.small()
|
||||
.checked(view.read(cx).config.right_click_copy_paste())
|
||||
.on_click(window.listener_for(&view, |this, checked, _, cx| {
|
||||
this.config.set_right_click_copy_paste(*checked);
|
||||
let _ = this.config.save();
|
||||
cx.notify();
|
||||
}))
|
||||
.into_any_element()
|
||||
}
|
||||
})
|
||||
).description(t!("copy_paste_hint", key = if cfg!(target_os = "macos") { "Command" } else { "Ctrl" }).to_string())
|
||||
)
|
||||
.item(
|
||||
SettingItem::new(
|
||||
t!("monitoring_position").to_string(),
|
||||
SettingField::render({
|
||||
let view = view_clone_for_general.clone();
|
||||
move |_, _window, cx| {
|
||||
Button::new("monitoring-position-dropdown")
|
||||
.small()
|
||||
.icon(IconName::PanelLeftOpen)
|
||||
.label({
|
||||
let pos = view.read(cx).config.monitoring_position().to_string();
|
||||
if pos == "Sidebar" {
|
||||
t!("position_sidebar").to_string()
|
||||
} else if pos == "Hidden" {
|
||||
t!("position_hidden").to_string()
|
||||
} else {
|
||||
t!("position_bottom").to_string()
|
||||
}
|
||||
})
|
||||
.dropdown_menu_with_anchor(Anchor::BottomRight, {
|
||||
let view = view.clone();
|
||||
move |mut menu, window, cx| {
|
||||
let pos = view.read(cx).config.monitoring_position().to_string();
|
||||
menu = menu.min_w(160.)
|
||||
.item(
|
||||
PopupMenuItem::new(t!("position_bottom").to_string())
|
||||
.checked(pos == "Bottom")
|
||||
.on_click(window.listener_for(&view, |this, _, _window, cx| {
|
||||
this.config.set_monitoring_position("Bottom");
|
||||
let _ = this.config.save();
|
||||
cx.notify();
|
||||
}))
|
||||
)
|
||||
.item(
|
||||
PopupMenuItem::new(t!("position_sidebar").to_string())
|
||||
.checked(pos == "Sidebar")
|
||||
.on_click(window.listener_for(&view, |this, _, _window, cx| {
|
||||
this.config.set_monitoring_position("Sidebar");
|
||||
let _ = this.config.save();
|
||||
cx.notify();
|
||||
}))
|
||||
)
|
||||
.item(
|
||||
PopupMenuItem::new(t!("position_hidden").to_string())
|
||||
.checked(pos == "Hidden")
|
||||
.on_click(window.listener_for(&view, |this, _, _window, cx| {
|
||||
this.config.set_monitoring_position("Hidden");
|
||||
let _ = this.config.save();
|
||||
cx.notify();
|
||||
}))
|
||||
);
|
||||
menu
|
||||
}
|
||||
})
|
||||
.into_any_element()
|
||||
}
|
||||
})
|
||||
)
|
||||
)
|
||||
.item(
|
||||
SettingItem::new(
|
||||
t!("language").to_string(),
|
||||
SettingField::render({
|
||||
let view = view_clone_for_general.clone();
|
||||
move |_, _window, cx| {
|
||||
Button::new("language-dropdown")
|
||||
.small()
|
||||
.icon(IconName::Globe)
|
||||
.label({
|
||||
let current_locale = view.read(cx).config.locale().to_string();
|
||||
if current_locale == "en" {
|
||||
t!("english").to_string()
|
||||
} else if current_locale == "zh-CN" {
|
||||
t!("chinese").to_string()
|
||||
} else {
|
||||
t!("follow_system").to_string()
|
||||
}
|
||||
})
|
||||
.dropdown_menu_with_anchor(Anchor::BottomRight, {
|
||||
let view = view.clone();
|
||||
move |mut menu, window, cx| {
|
||||
let current_locale = view.read(cx).config.locale().to_string();
|
||||
menu = menu.min_w(160.)
|
||||
.item(
|
||||
PopupMenuItem::new(t!("follow_system").to_string())
|
||||
.checked(current_locale == "system")
|
||||
.on_click(window.listener_for(&view, |this, _, window, cx| {
|
||||
this.set_display_language("system", window, cx)
|
||||
}))
|
||||
)
|
||||
.separator()
|
||||
.item(
|
||||
PopupMenuItem::new(t!("english").to_string())
|
||||
.checked(current_locale == "en")
|
||||
.on_click(window.listener_for(&view, |this, _, window, cx| {
|
||||
this.set_display_language("en", window, cx)
|
||||
}))
|
||||
)
|
||||
.item(
|
||||
PopupMenuItem::new(t!("chinese").to_string())
|
||||
.checked(current_locale == "zh-CN")
|
||||
.on_click(window.listener_for(&view, |this, _, window, cx| {
|
||||
this.set_display_language("zh-CN", window, cx)
|
||||
}))
|
||||
);
|
||||
menu
|
||||
}
|
||||
})
|
||||
.into_any_element()
|
||||
}
|
||||
})
|
||||
)
|
||||
)
|
||||
.item(
|
||||
SettingItem::new(
|
||||
t!("reset_layout").to_string(),
|
||||
SettingField::render({
|
||||
let view = view_clone_for_general.clone();
|
||||
move |_, window, _cx| {
|
||||
Button::new("reset-layout")
|
||||
.small()
|
||||
.label(t!("reset").to_string())
|
||||
.on_click(window.listener_for(&view, |this, _, window, cx| {
|
||||
this.reset_layout(window, cx);
|
||||
}))
|
||||
.into_any_element()
|
||||
}
|
||||
})
|
||||
).description(t!("reset_layout_hint").to_string())
|
||||
)
|
||||
)
|
||||
)
|
||||
.child(
|
||||
h_flex()
|
||||
.items_center()
|
||||
.gap_3()
|
||||
.child(div().w(px(240.)).child(t!("monitoring_position").to_string()))
|
||||
.child(
|
||||
Button::new("monitoring-position-dropdown")
|
||||
.small()
|
||||
.icon(IconName::PanelLeftOpen)
|
||||
.label({
|
||||
let pos = view.read(cx).config.monitoring_position().to_string();
|
||||
if pos == "Sidebar" {
|
||||
t!("position_sidebar").to_string()
|
||||
} else if pos == "Hidden" {
|
||||
t!("position_hidden").to_string()
|
||||
} else {
|
||||
t!("position_bottom").to_string()
|
||||
}
|
||||
})
|
||||
.dropdown_menu_with_anchor(Anchor::BottomRight, {
|
||||
let view = view.clone();
|
||||
move |mut menu, window, cx| {
|
||||
let pos = view.read(cx).config.monitoring_position().to_string();
|
||||
menu = menu
|
||||
.min_w(160.)
|
||||
.item(
|
||||
PopupMenuItem::new(t!("position_bottom").to_string())
|
||||
.checked(pos == "Bottom")
|
||||
.on_click(window.listener_for(
|
||||
&view,
|
||||
|this, _, _window, cx| {
|
||||
this.config.set_monitoring_position("Bottom");
|
||||
let _ = this.config.save();
|
||||
cx.notify();
|
||||
},
|
||||
)),
|
||||
)
|
||||
.item(
|
||||
PopupMenuItem::new(t!("position_sidebar").to_string())
|
||||
.checked(pos == "Sidebar")
|
||||
.on_click(window.listener_for(
|
||||
&view,
|
||||
|this, _, _window, cx| {
|
||||
this.config.set_monitoring_position("Sidebar");
|
||||
let _ = this.config.save();
|
||||
cx.notify();
|
||||
},
|
||||
)),
|
||||
)
|
||||
.item(
|
||||
PopupMenuItem::new(t!("position_hidden").to_string())
|
||||
.checked(pos == "Hidden")
|
||||
.on_click(window.listener_for(
|
||||
&view,
|
||||
|this, _, _window, cx| {
|
||||
this.config.set_monitoring_position("Hidden");
|
||||
let _ = this.config.save();
|
||||
cx.notify();
|
||||
},
|
||||
)),
|
||||
);
|
||||
menu
|
||||
}
|
||||
}),
|
||||
.page(
|
||||
SettingPage::new(t!("settings_key_bindings").to_string())
|
||||
.icon(IconName::SquareTerminal)
|
||||
)
|
||||
.page(
|
||||
SettingPage::new(t!("settings_help").to_string())
|
||||
.icon(IconName::BookOpen)
|
||||
)
|
||||
.page(
|
||||
SettingPage::new(t!("settings_about").to_string())
|
||||
.icon(IconName::Info)
|
||||
.group(
|
||||
SettingGroup::new()
|
||||
.item(SettingItem::render(move |_, _window, cx| {
|
||||
v_flex()
|
||||
.gap_2()
|
||||
.items_center()
|
||||
.child(div().text_size(rems(1.5)).font_weight(FontWeight::BOLD).child("Ashell"))
|
||||
.child(div().text_size(rems(0.9)).child(format!("Version {}", version)))
|
||||
.child(
|
||||
div()
|
||||
.text_size(rems(0.9))
|
||||
.text_color(cx.theme().muted_foreground)
|
||||
.child("A GPUI Component based SSH and local terminal client"),
|
||||
)
|
||||
.child(
|
||||
div()
|
||||
.text_size(rems(0.9))
|
||||
.text_color(cx.theme().muted_foreground)
|
||||
.child(t!("about_feedback_hint")),
|
||||
)
|
||||
.child(
|
||||
Button::new("github-link")
|
||||
.label("https://github.com/rust-kotlin/ashell")
|
||||
.ghost()
|
||||
.on_click(|_, _window, _cx| {
|
||||
let _ = open::that("https://github.com/rust-kotlin/ashell");
|
||||
}),
|
||||
)
|
||||
}))
|
||||
)
|
||||
)
|
||||
.child(
|
||||
h_flex()
|
||||
.items_center()
|
||||
.gap_3()
|
||||
.child(div().w(px(240.)).child(t!("language").to_string()))
|
||||
.child(
|
||||
Button::new("language-dropdown")
|
||||
.small()
|
||||
.icon(IconName::Globe)
|
||||
.label({
|
||||
let current_locale =
|
||||
view.read(cx).config.locale().to_string();
|
||||
if current_locale == "en" {
|
||||
t!("english").to_string()
|
||||
} else if current_locale == "zh-CN" {
|
||||
t!("chinese").to_string()
|
||||
} else {
|
||||
t!("follow_system").to_string()
|
||||
}
|
||||
})
|
||||
.dropdown_menu_with_anchor(Anchor::BottomRight, {
|
||||
let view = view.clone();
|
||||
move |mut menu, window, cx| {
|
||||
let current_locale = view
|
||||
.read(cx)
|
||||
.config
|
||||
.locale()
|
||||
.to_string();
|
||||
menu = menu
|
||||
.min_w(160.)
|
||||
.item(
|
||||
PopupMenuItem::new(
|
||||
t!("follow_system").to_string(),
|
||||
)
|
||||
.checked(current_locale == "system")
|
||||
.on_click(window.listener_for(
|
||||
&view,
|
||||
|this, _, window, cx| {
|
||||
this.set_display_language(
|
||||
"system", window, cx,
|
||||
)
|
||||
},
|
||||
)),
|
||||
)
|
||||
.separator()
|
||||
.item(
|
||||
PopupMenuItem::new(
|
||||
t!("english").to_string(),
|
||||
)
|
||||
.checked(current_locale == "en")
|
||||
.on_click(window.listener_for(
|
||||
&view,
|
||||
|this, _, window, cx| {
|
||||
this.set_display_language(
|
||||
"en", window, cx,
|
||||
)
|
||||
},
|
||||
)),
|
||||
)
|
||||
.item(
|
||||
PopupMenuItem::new(
|
||||
t!("chinese").to_string(),
|
||||
)
|
||||
.checked(current_locale == "zh-CN")
|
||||
.on_click(window.listener_for(
|
||||
&view,
|
||||
|this, _, window, cx| {
|
||||
this.set_display_language(
|
||||
"zh-CN", window, cx,
|
||||
)
|
||||
},
|
||||
)),
|
||||
);
|
||||
menu
|
||||
}
|
||||
}),
|
||||
),
|
||||
)
|
||||
.child(
|
||||
h_flex()
|
||||
.items_center()
|
||||
.gap_3()
|
||||
.child(
|
||||
div().w(px(240.)).child(t!("reset_layout").to_string()),
|
||||
)
|
||||
.child(
|
||||
Button::new("reset-layout")
|
||||
.label(t!("reset").to_string())
|
||||
.on_click(window.listener_for(
|
||||
&view,
|
||||
|this, _, window, cx| {
|
||||
this.reset_layout(window, cx);
|
||||
},
|
||||
)),
|
||||
),
|
||||
)
|
||||
.child(
|
||||
div()
|
||||
.text_size(rems(1.0))
|
||||
.text_color(cx.theme().muted_foreground)
|
||||
.child(t!("theme_management_hint")),
|
||||
),
|
||||
)
|
||||
}
|
||||
})
|
||||
|
||||
@@ -217,6 +217,7 @@ pub(crate) struct Ashell {
|
||||
pub(crate) selector_selection: usize,
|
||||
pub(crate) workspace_panels: Entity<ResizableState>,
|
||||
pub(crate) body_panels: Entity<ResizableState>,
|
||||
pub(crate) is_layout_reset: bool,
|
||||
pub(crate) terminal_scrollbars: HashMap<String, TerminalScrollbarHandle>,
|
||||
pub(crate) remote_files_scroll_handle: UniformListScrollHandle,
|
||||
pub(crate) disk_scroll_handle: gpui::ScrollHandle,
|
||||
@@ -398,6 +399,7 @@ impl Ashell {
|
||||
selector_selection: 0,
|
||||
workspace_panels,
|
||||
body_panels,
|
||||
is_layout_reset: false,
|
||||
terminal_scrollbars: HashMap::new(),
|
||||
remote_files_scroll_handle: UniformListScrollHandle::new(),
|
||||
disk_scroll_handle: gpui::ScrollHandle::new(),
|
||||
@@ -813,3 +815,4 @@ impl Ashell {
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -221,7 +221,12 @@ 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.");
|
||||
return true;
|
||||
}
|
||||
tracing::info!("[ui] main application window closed, saving layout state...");
|
||||
let mut config = ConfigStore::load().unwrap_or_else(|_| ConfigStore::in_memory());
|
||||
let current_bounds = window.window_bounds();
|
||||
|
||||
+2
-92
@@ -1,11 +1,8 @@
|
||||
use anyhow::{Context as _, Result};
|
||||
use gpui::{Anchor, App, Context, IntoElement, SharedString, Window, px};
|
||||
use gpui::{App, Context, SharedString, Window, px};
|
||||
use gpui_component::{
|
||||
ActiveTheme as _, IconName, Sizable as _, Theme, ThemeMode, ThemeRegistry,
|
||||
button::{Button, ButtonVariants as _},
|
||||
menu::{DropdownMenu as _, PopupMenuItem},
|
||||
ActiveTheme as _, Theme, ThemeMode, ThemeRegistry,
|
||||
};
|
||||
use rust_i18n::t;
|
||||
|
||||
use crate::Ashell;
|
||||
|
||||
@@ -147,92 +144,5 @@ impl Ashell {
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn theme_dropdown(&self, cx: &mut Context<Self>) -> impl IntoElement {
|
||||
let view = cx.entity();
|
||||
let themes = ThemeRegistry::global(cx)
|
||||
.sorted_themes()
|
||||
.into_iter()
|
||||
.cloned()
|
||||
.collect::<Vec<_>>();
|
||||
let light_themes = themes
|
||||
.iter()
|
||||
.filter(|theme| !theme.mode.is_dark())
|
||||
.map(|theme| theme.name.clone())
|
||||
.collect::<Vec<_>>();
|
||||
let dark_themes = themes
|
||||
.iter()
|
||||
.filter(|theme| theme.mode.is_dark())
|
||||
.map(|theme| theme.name.clone())
|
||||
.collect::<Vec<_>>();
|
||||
let follow_system = self.follow_system_theme;
|
||||
let is_dark_mode = cx.theme().mode.is_dark();
|
||||
let light_theme_name = self.light_theme_name.clone();
|
||||
let dark_theme_name = self.dark_theme_name.clone();
|
||||
let icon = if follow_system {
|
||||
IconName::Sun
|
||||
} else if is_dark_mode {
|
||||
IconName::Moon
|
||||
} else {
|
||||
IconName::Sun
|
||||
};
|
||||
|
||||
Button::new("theme-dropdown")
|
||||
.ghost()
|
||||
.small()
|
||||
.icon(icon)
|
||||
.dropdown_menu_with_anchor(Anchor::BottomRight, move |mut menu, window, _| {
|
||||
menu = menu
|
||||
.min_w(220.)
|
||||
.item(
|
||||
PopupMenuItem::new(t!("follow_system"))
|
||||
.checked(follow_system)
|
||||
.on_click(window.listener_for(&view, |this, _, window, cx| {
|
||||
this.set_follow_system_theme(true, window, cx)
|
||||
})),
|
||||
)
|
||||
.item(
|
||||
PopupMenuItem::new(t!("use_light_mode"))
|
||||
.checked(!follow_system && !is_dark_mode)
|
||||
.on_click(window.listener_for(&view, |this, _, window, cx| {
|
||||
this.switch_theme_mode(ThemeMode::Light, window, cx)
|
||||
})),
|
||||
)
|
||||
.item(
|
||||
PopupMenuItem::new(t!("use_dark_mode"))
|
||||
.checked(!follow_system && is_dark_mode)
|
||||
.on_click(window.listener_for(&view, |this, _, window, cx| {
|
||||
this.switch_theme_mode(ThemeMode::Dark, window, cx)
|
||||
})),
|
||||
)
|
||||
.separator()
|
||||
.label(t!("light_theme").to_string());
|
||||
|
||||
for theme_name in light_themes.clone() {
|
||||
let checked = theme_name == light_theme_name;
|
||||
menu = menu.item(
|
||||
PopupMenuItem::new(theme_name.clone())
|
||||
.checked(checked)
|
||||
.on_click(window.listener_for(&view, move |this, _, window, cx| {
|
||||
this.apply_theme(theme_name.clone(), window, cx)
|
||||
})),
|
||||
);
|
||||
}
|
||||
|
||||
menu = menu.separator();
|
||||
menu = menu.label(t!("dark_theme").to_string());
|
||||
|
||||
for theme_name in dark_themes.clone() {
|
||||
let checked = theme_name == dark_theme_name;
|
||||
menu = menu.item(
|
||||
PopupMenuItem::new(theme_name.clone())
|
||||
.checked(checked)
|
||||
.on_click(window.listener_for(&view, move |this, _, window, cx| {
|
||||
this.apply_theme(theme_name.clone(), window, cx)
|
||||
})),
|
||||
);
|
||||
}
|
||||
|
||||
menu
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -1209,7 +1209,7 @@ impl Ashell {
|
||||
this.show_settings_dialog(window, cx)
|
||||
})),
|
||||
)
|
||||
.child(self.theme_dropdown(cx)),
|
||||
|
||||
)
|
||||
.child(
|
||||
div()
|
||||
|
||||
+1
-1
@@ -285,10 +285,10 @@ impl Ashell {
|
||||
self.config.set_layout_state(None, None, None);
|
||||
let _ = self.config.save();
|
||||
|
||||
self.is_layout_reset = true;
|
||||
self.workspace_panels = cx.new(|_| gpui_component::resizable::ResizableState::default());
|
||||
self.body_panels = cx.new(|_| gpui_component::resizable::ResizableState::default());
|
||||
|
||||
self.status = t!("reset_layout_success").into();
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user