feat: settings dialog using Settings component #19

This commit is contained in:
TomZz
2026-06-14 19:34:06 +08:00
parent bc6af2e213
commit bbf843dde6
8 changed files with 515 additions and 547 deletions
+11 -3
View File
@@ -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
View File
@@ -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
View File
@@ -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(&current)
{
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(&current) {
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")),
),
)
}
})
+3
View File
@@ -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 {
))
}
}
+5
View File
@@ -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
View File
@@ -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
View File
@@ -1209,7 +1209,7 @@ impl Ashell {
this.show_settings_dialog(window, cx)
})),
)
.child(self.theme_dropdown(cx)),
)
.child(
div()
+1 -1
View File
@@ -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();
}