Compare commits

...
8 changed files with 59 additions and 5 deletions

No files matched your search

+8
View File
@@ -578,6 +578,8 @@ pub struct AppearanceSettings {
pub main_menu_style: String,
/// A lone Alt press focuses or opens the main menu (IDEA `ide.windows.main.menu.focus.on.alt`).
pub main_menu_alt: bool,
/// Always show the main menu titles instead of the hamburger button (#1521).
pub always_show_main_menu: bool,
/// `subpixel` | `greyscale` | `none`
pub ide_antialiasing: String,
pub editor_antialiasing: String,
@@ -655,6 +657,7 @@ impl Default for AppearanceSettings {
menu_icons: true,
main_menu_style: "hamburger".into(),
main_menu_alt: true,
always_show_main_menu: false,
ide_antialiasing: "greyscale".into(),
editor_antialiasing: "greyscale".into(),
show_tool_window_bars: true,
@@ -1570,6 +1573,11 @@ mod tests {
let off: Settings = serde_json::from_str(r#"{"appearance":{"mainMenuAlt":false}}"#).unwrap();
assert!(!off.appearance.main_menu_alt);
assert_eq!(serde_json::to_value(off).unwrap()["appearance"]["mainMenuAlt"], false);
let old: Settings = serde_json::from_str(r#"{"appearance":{"compactMode":true}}"#).unwrap();
assert!(!old.appearance.always_show_main_menu);
let shown: Settings = serde_json::from_str(r#"{"appearance":{"alwaysShowMainMenu":true}}"#).unwrap();
assert!(shown.appearance.always_show_main_menu);
assert_eq!(serde_json::to_value(shown).unwrap()["appearance"]["alwaysShowMainMenu"], true);
}
#[test]
+2
View File
@@ -2110,6 +2110,8 @@ export type AppearanceSettings = {
mainMenuStyle?: string,
/** A lone Alt press focuses or opens the main menu (IDEA `ide.windows.main.menu.focus.on.alt`). */
mainMenuAlt?: boolean,
/** Always show the main menu titles instead of the hamburger button (#1521). */
alwaysShowMainMenu?: boolean,
/** `subpixel` | `greyscale` | `none` */
ideAntialiasing?: string,
editorAntialiasing?: string,
@@ -43,4 +43,9 @@ describe("Main menu appearance setting", () => {
expect(validateAppearanceExtras({ mainMenuStyle: "unknown" }).mainMenuStyle).toBe("hamburger");
expect(validateAppearanceExtras({ mainMenuStyle: "merged" }).mainMenuStyle).toBe("merged");
});
it("defaults to hiding the hamburger override and round-trips it (#1521)", () => {
expect(validateAppearanceExtras({}).alwaysShowMainMenu).toBe(false);
expect(validateAppearanceExtras({ alwaysShowMainMenu: true }).alwaysShowMainMenu).toBe(true);
});
});
@@ -40,6 +40,8 @@ export interface AppearanceExtras {
mainMenuStyle: "hamburger" | "merged";
/** A lone Alt press focuses or opens the main menu (IDEA `ide.windows.main.menu.focus.on.alt`). */
mainMenuAlt: boolean;
/** Always show the main menu titles instead of the hamburger button (#1521). */
alwaysShowMainMenu: boolean;
ideAntialiasing: Antialiasing;
editorAntialiasing: Antialiasing;
showToolWindowBars: boolean;
@@ -107,6 +109,7 @@ export const DEFAULT_APPEARANCE_EXTRAS: AppearanceExtras = {
menuIcons: true,
mainMenuStyle: "hamburger",
mainMenuAlt: true,
alwaysShowMainMenu: false,
ideAntialiasing: "greyscale",
editorAntialiasing: "greyscale",
showToolWindowBars: true,
@@ -171,6 +174,7 @@ export function validateAppearanceExtras(raw: unknown): AppearanceExtras {
menuIcons: b("menuIcons"),
mainMenuStyle: oneOf(a.mainMenuStyle, ["hamburger", "merged"], d.mainMenuStyle),
mainMenuAlt: b("mainMenuAlt"),
alwaysShowMainMenu: b("alwaysShowMainMenu"),
ideAntialiasing: oneOf(a.ideAntialiasing, AA, d.ideAntialiasing),
editorAntialiasing: oneOf(a.editorAntialiasing, AA, d.editorAntialiasing),
showToolWindowBars: b("showToolWindowBars"),
+5
View File
@@ -358,6 +358,11 @@ function UiOptionsGrid() {
label="Open main menu with a lone Alt press"
hint="When off, Alt key combinations still work but pressing Alt alone does not focus the main menu"
/>
<BoundToggle
k="appearance.alwaysShowMainMenu"
label="Always show the main menu"
hint="Show menu titles instead of the hamburger button, even when the window is narrow"
/>
</>
}
/>
+2
View File
@@ -6985,6 +6985,8 @@
"Servers": "服务器",
"Open main menu with a lone Alt press": "单独按下 Alt 打开主菜单",
"When off, Alt key combinations still work but pressing Alt alone does not focus the main menu": "关闭后,Alt 组合键仍然可用,但单独按下 Alt 不会聚焦主菜单",
"Always show the main menu": "始终显示主菜单",
"Show menu titles instead of the hamburger button, even when the window is narrow": "显示菜单标题而不是汉堡菜单按钮,即使窗口较窄",
"the bundle of {gemfile} has no `debug` gem. Add `gem \"debug\"` to the Gemfile and run `bundle install`": "{gemfile} 的 bundle 中没有 `debug` gem。请在 Gemfile 中添加 `gem \"debug\"`,然后运行 `bundle install`",
"No background tasks": "没有后台任务",
"Status Bar Widgets": "状态栏微件",
@@ -253,6 +253,30 @@ describe("main menu presentation", () => {
expect(screen.getByRole("button", { name: "Main Menu" })).toBeInTheDocument();
});
it.each(["hamburger", "merged"] as const)(
"%s shows titles instead of the hamburger button when always showing the main menu",
async (style) => {
// A narrow window where merged titles would not fit: the flag keeps them visible anyway.
if (style === "merged") available = 100;
act(() =>
useProject.setState({
settings: { appearance: { mainMenuStyle: style, alwaysShowMainMenu: true } } as Settings,
}),
);
render(<MainMenu />);
await screen.findByRole("menubar");
expect(screen.queryByRole("button", { name: "Main Menu" })).toBeNull();
expect(titles().getByRole("menuitem", { name: "File" })).toBeInTheDocument();
// Dismissing a menu keeps the titles instead of collapsing back to the button.
fireEvent.click(titles().getByRole("menuitem", { name: "File" }));
expect(await screen.findByRole("menu", { name: "File" })).toBeInTheDocument();
fireEvent.keyDown(document.activeElement!, { key: "Escape" });
expect(screen.queryByRole("menu")).toBeNull();
expect(screen.getByRole("menubar")).toBeInTheDocument();
expect(screen.queryByRole("button", { name: "Main Menu" })).toBeNull();
},
);
it("applies the setting immediately and restores toolbar visibility when switching from an expanded hamburger", async () => {
const expanded = vi.fn();
render(<MainMenu onExpandedChange={expanded} />);
+9 -5
View File
@@ -96,19 +96,22 @@ function MainMenuBar({ merged, onExpandedChange }: MainMenuProps & { merged: boo
const previousFocus = useRef<HTMLElement | null>(null);
const [fits, setFits] = useState(false);
const pressedTitle = useRef(false);
const persistent = merged && fits;
// #1521: always show the menu titles instead of the hamburger button,
// even when the window is too narrow for the merged titles to fit.
const alwaysShow = useProject((s) => s.settings?.appearance?.alwaysShowMainMenu === true);
const persistent = (merged && fits) || alwaysShow;
const showTitles = persistent || (!merged && expanded);
useEffect(() => {
let disposed = false;
if (merged)
if (merged || alwaysShow)
void loadMenus().then((top) => {
if (!disposed) setMenus(top);
});
return () => {
disposed = true;
};
}, [merged]);
}, [merged, alwaysShow]);
useEffect(() => {
if (!merged || open === null) return;
let disposed = false;
@@ -132,15 +135,16 @@ function MainMenuBar({ merged, onExpandedChange }: MainMenuProps & { merged: boo
const next = menus.length > 0 && slot.current!.clientWidth >= measure.current!.scrollWidth;
setFits(next);
// Each presentation's menu closes with it, and must not reopen when it returns.
// With "always show" the titles never collapse, so an open menu stays open.
if (next) setExpanded(false);
else setOpen(null);
else if (!alwaysShow) setOpen(null);
};
const observer = new ResizeObserver(update);
observer.observe(slot.current);
observer.observe(measure.current);
update();
return () => observer.disconnect();
}, [merged, menus]);
}, [merged, menus, alwaysShow]);
const close = () => {
setOpen(null);