From 1a2cd210a0a145e160e8df741a0f1c4eb6b9cf66 Mon Sep 17 00:00:00 2001 From: SteVen Batten Date: Wed, 7 Nov 2018 15:36:09 -0800 Subject: [PATCH 1/3] snapshot --- src/vs/base/browser/ui/menu/menu.css | 10 +++++ src/vs/base/browser/ui/menu/menu.ts | 55 +++++++++++++++++++++++++++- 2 files changed, 63 insertions(+), 2 deletions(-) diff --git a/src/vs/base/browser/ui/menu/menu.css b/src/vs/base/browser/ui/menu/menu.css index ecbb197f55c9a..ac12d097257ef 100644 --- a/src/vs/base/browser/ui/menu/menu.css +++ b/src/vs/base/browser/ui/menu/menu.css @@ -74,6 +74,16 @@ margin: 0; } +.monaco-menu .monaco-action-bar.vertical .action-item { + position: static; + overflow: visible; +} + + +.monaco-menu .monaco-action-bar.vertical .action-item .monaco-submenu { + position: absolute; +} + .monaco-menu .monaco-action-bar.vertical .action-label.separator { padding: 0.5em 0 0 0; margin-bottom: 0.5em; diff --git a/src/vs/base/browser/ui/menu/menu.ts b/src/vs/base/browser/ui/menu/menu.ts index 75554c14fd5ed..3a3846ea8a31b 100644 --- a/src/vs/base/browser/ui/menu/menu.ts +++ b/src/vs/base/browser/ui/menu/menu.ts @@ -14,6 +14,9 @@ import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { RunOnceScheduler } from 'vs/base/common/async'; import { IDisposable, dispose } from 'vs/base/common/lifecycle'; import { Color } from 'vs/base/common/color'; +import { DomScrollableElement } from 'vs/base/browser/ui/scrollbar/scrollableElement'; +import { ScrollbarVisibility } from 'vs/base/common/scrollable'; +import { Event, Emitter } from 'vs/base/common/event'; export const MENU_MNEMONIC_REGEX: RegExp = /\(&{1,2}(.)\)|&{1,2}(.)/; export const MENU_ESCAPED_MNEMONIC_REGEX: RegExp = /(?:&){1,2}(.)/; @@ -52,15 +55,18 @@ interface ISubMenuData { export class Menu extends ActionBar { private mnemonics: Map>; private menuDisposables: IDisposable[]; + private scrollableElement: DomScrollableElement; + private menuContainer: HTMLElement; + + private readonly _onScroll: Emitter; constructor(container: HTMLElement, actions: IAction[], options: IMenuOptions = {}) { addClass(container, 'monaco-menu-container'); container.setAttribute('role', 'presentation'); - let menuContainer = document.createElement('div'); + const menuContainer = document.createElement('div'); addClass(menuContainer, 'monaco-menu'); menuContainer.setAttribute('role', 'presentation'); - container.appendChild(menuContainer); super(menuContainer, { orientation: ActionsOrientation.VERTICAL, @@ -71,6 +77,8 @@ export class Menu extends ActionBar { triggerKeys: { keys: [KeyCode.Enter], keyDown: true } }); + this._onScroll = new Emitter(); + this.actionsList.setAttribute('role', 'menu'); this.actionsList.tabIndex = 0; @@ -138,7 +146,35 @@ export class Menu extends ActionBar { this.mnemonics = new Map>(); + this.menuContainer = menuContainer; + this.push(actions, { icon: true, label: true, isMenu: true }); + + // Scroll Logic + this.scrollableElement = new DomScrollableElement(menuContainer, { + alwaysConsumeMouseWheel: true, + horizontal: ScrollbarVisibility.Hidden, + vertical: ScrollbarVisibility.Auto, + verticalScrollbarSize: 5, + handleMouseWheel: true, + useShadows: true, + + }); + + const scrollElement = this.scrollableElement.getDomNode(); + scrollElement.style.position = null; + + // menuContainer.style.maxHeight = '300px'; + console.log(`${window.innerHeight}px`); + console.log(`${container.offsetTop}px`); + menuContainer.style.maxHeight = `${Math.max(10, window.innerHeight - container.offsetTop - 10)}px`; + + this.scrollableElement.onScroll(() => { + this._onScroll.fire(); + }, this, this.menuDisposables); + + container.appendChild(this.scrollableElement.getDomNode()); + this.scrollableElement.scanDomNode(); } style(style: IMenuStyles): void { @@ -163,6 +199,15 @@ export class Menu extends ActionBar { } } + public get onScroll(): Event { + return this._onScroll.event; + } + + get scrollOffset(): number { + console.log(`scroll top: ${this.menuContainer.scrollTop}px`); + return this.menuContainer.scrollTop; + } + private focusItemByElement(element: HTMLElement) { const lastFocusedItem = this.focusedItem; this.setFocusedItem(element); @@ -501,6 +546,11 @@ class SubmenuActionItem extends MenuActionItem { this.hideScheduler.schedule(); } })); + + this._register(this.parentData.parent.onScroll(() => { + this.parentData.parent.focus(false); + this.cleanupExistingSubmenu(false); + })); } onClick(e: EventLike): void { @@ -528,6 +578,7 @@ class SubmenuActionItem extends MenuActionItem { this.submenuContainer = append(this.element, $('div.monaco-submenu')); addClasses(this.submenuContainer, 'menubar-menu-items-holder', 'context-view'); this.submenuContainer.style.left = `${getClientArea(this.element).width}px`; + this.submenuContainer.style.top = `${this.element.offsetTop - this.parentData.parent.scrollOffset}px`; this.submenuDisposables.push(addDisposableListener(this.submenuContainer, EventType.KEY_UP, e => { let event = new StandardKeyboardEvent(e as KeyboardEvent); From da07dd31018498ed6b6855378e1b6296247a6566 Mon Sep 17 00:00:00 2001 From: SteVen Batten Date: Thu, 8 Nov 2018 15:44:30 -0800 Subject: [PATCH 2/3] checked items, disposal, restrict heights --- src/vs/base/browser/ui/menu/menu.css | 1 + src/vs/base/browser/ui/menu/menu.ts | 44 ++++++++++++++++------------ 2 files changed, 27 insertions(+), 18 deletions(-) diff --git a/src/vs/base/browser/ui/menu/menu.css b/src/vs/base/browser/ui/menu/menu.css index ac12d097257ef..c24040ba3a385 100644 --- a/src/vs/base/browser/ui/menu/menu.css +++ b/src/vs/base/browser/ui/menu/menu.css @@ -30,6 +30,7 @@ display: flex; height: 2em; align-items: center; + position: relative; } .monaco-menu .monaco-action-bar.vertical .action-label { diff --git a/src/vs/base/browser/ui/menu/menu.ts b/src/vs/base/browser/ui/menu/menu.ts index 3a3846ea8a31b..0c89dd516fe87 100644 --- a/src/vs/base/browser/ui/menu/menu.ts +++ b/src/vs/base/browser/ui/menu/menu.ts @@ -56,7 +56,7 @@ export class Menu extends ActionBar { private mnemonics: Map>; private menuDisposables: IDisposable[]; private scrollableElement: DomScrollableElement; - private menuContainer: HTMLElement; + private menuElement: HTMLElement; private readonly _onScroll: Emitter; @@ -64,11 +64,11 @@ export class Menu extends ActionBar { addClass(container, 'monaco-menu-container'); container.setAttribute('role', 'presentation'); - const menuContainer = document.createElement('div'); - addClass(menuContainer, 'monaco-menu'); - menuContainer.setAttribute('role', 'presentation'); + const menuElement = document.createElement('div'); + addClass(menuElement, 'monaco-menu'); + menuElement.setAttribute('role', 'presentation'); - super(menuContainer, { + super(menuElement, { orientation: ActionsOrientation.VERTICAL, actionItemProvider: action => this.doGetActionItem(action, options, parentData), context: options.context, @@ -77,6 +77,8 @@ export class Menu extends ActionBar { triggerKeys: { keys: [KeyCode.Enter], keyDown: true } }); + this.menuElement = menuElement; + this._onScroll = new Emitter(); this.actionsList.setAttribute('role', 'menu'); @@ -86,7 +88,7 @@ export class Menu extends ActionBar { this.menuDisposables = []; if (options.enableMnemonics) { - this.menuDisposables.push(addDisposableListener(menuContainer, EventType.KEY_DOWN, (e) => { + this.menuDisposables.push(addDisposableListener(menuElement, EventType.KEY_DOWN, (e) => { const key = KeyCodeUtils.fromString(e.key); if (this.mnemonics.has(key)) { EventHelper.stop(e, true); @@ -146,28 +148,22 @@ export class Menu extends ActionBar { this.mnemonics = new Map>(); - this.menuContainer = menuContainer; - this.push(actions, { icon: true, label: true, isMenu: true }); // Scroll Logic - this.scrollableElement = new DomScrollableElement(menuContainer, { + this.scrollableElement = this._register(new DomScrollableElement(menuElement, { alwaysConsumeMouseWheel: true, horizontal: ScrollbarVisibility.Hidden, vertical: ScrollbarVisibility.Auto, verticalScrollbarSize: 5, handleMouseWheel: true, - useShadows: true, - - }); + useShadows: true + })); const scrollElement = this.scrollableElement.getDomNode(); scrollElement.style.position = null; - // menuContainer.style.maxHeight = '300px'; - console.log(`${window.innerHeight}px`); - console.log(`${container.offsetTop}px`); - menuContainer.style.maxHeight = `${Math.max(10, window.innerHeight - container.offsetTop - 10)}px`; + menuElement.style.maxHeight = `${Math.max(10, window.innerHeight - container.getBoundingClientRect().top - 30)}px`; this.scrollableElement.onScroll(() => { this._onScroll.fire(); @@ -199,13 +195,16 @@ export class Menu extends ActionBar { } } + getContainer(): HTMLElement { + return this.scrollableElement.getDomNode(); + } + public get onScroll(): Event { return this._onScroll.event; } get scrollOffset(): number { - console.log(`scroll top: ${this.menuContainer.scrollTop}px`); - return this.menuContainer.scrollTop; + return this.menuElement.scrollTop; } private focusItemByElement(element: HTMLElement) { @@ -455,6 +454,10 @@ class MenuActionItem extends BaseActionItem { } protected applyStyle(): void { + if (!this.menuStyle) { + return; + } + const isSelected = hasClass(this.element, 'focused'); const fgColor = isSelected && this.menuStyle.selectionForegroundColor ? this.menuStyle.selectionForegroundColor : this.menuStyle.foregroundColor; const bgColor = isSelected && this.menuStyle.selectionBackgroundColor ? this.menuStyle.selectionBackgroundColor : this.menuStyle.backgroundColor; @@ -625,6 +628,11 @@ class SubmenuActionItem extends MenuActionItem { protected applyStyle(): void { super.applyStyle(); + + if (!this.menuStyle) { + return; + } + const isSelected = hasClass(this.element, 'focused'); const fgColor = isSelected && this.menuStyle.selectionForegroundColor ? this.menuStyle.selectionForegroundColor : this.menuStyle.foregroundColor; From 0a8da5f55be61ccf9f95d14e4426600368c09df7 Mon Sep 17 00:00:00 2001 From: SteVen Batten Date: Mon, 26 Nov 2018 11:36:28 -0800 Subject: [PATCH 3/3] Addressing feedback --- src/vs/base/browser/ui/menu/menu.ts | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/vs/base/browser/ui/menu/menu.ts b/src/vs/base/browser/ui/menu/menu.ts index 25fe2ed7a58d2..bc037e06e14ce 100644 --- a/src/vs/base/browser/ui/menu/menu.ts +++ b/src/vs/base/browser/ui/menu/menu.ts @@ -81,7 +81,7 @@ export class Menu extends ActionBar { this.menuElement = menuElement; - this._onScroll = new Emitter(); + this._onScroll = this._register(new Emitter()); this.actionsList.setAttribute('role', 'menu'); @@ -156,8 +156,8 @@ export class Menu extends ActionBar { this.scrollableElement = this._register(new DomScrollableElement(menuElement, { alwaysConsumeMouseWheel: true, horizontal: ScrollbarVisibility.Hidden, - vertical: ScrollbarVisibility.Auto, - verticalScrollbarSize: 5, + vertical: ScrollbarVisibility.Visible, + verticalScrollbarSize: 7, handleMouseWheel: true, useShadows: true })); @@ -171,6 +171,10 @@ export class Menu extends ActionBar { this._onScroll.fire(); }, this, this.menuDisposables); + this._register(addDisposableListener(this.menuElement, EventType.SCROLL, (e) => { + this.scrollableElement.scanDomNode(); + })); + container.appendChild(this.scrollableElement.getDomNode()); this.scrollableElement.scanDomNode();