/// <summary> /// Create a dropdown menu under the text. /// </summary> public override void showEditor_(bool opt_quietInput) { WidgetDiv.show(this, this.sourceBlock_.RTL, null); var thisField = this; var callback = new Action <goog.events.Event>((e) => { var menuItem = (goog.ui.MenuItem)e.target; if (menuItem != null) { var value = menuItem.getValue(); if (thisField.sourceBlock_ != null) { // Call any validation function, and allow it to override. value = thisField.callValidator(value); } if (value != null) { thisField.setValue(value); } } WidgetDiv.hideIfOwner(thisField); }); var menu = new goog.ui.Menu(); menu.setRightToLeft(this.sourceBlock_.RTL); var options = this.getOptions_(); for (var i = 0; i < options.Length; i++) { var text = options[i].text; // Human-readable text. var value = options[i].value; // Language-neutral value. var menuItem = new goog.ui.MenuItem(text); menuItem.setRightToLeft(this.sourceBlock_.RTL); menuItem.setValue(value); menuItem.setCheckable(true); menu.addChild(menuItem, true); menuItem.setChecked(value == this.value_); } // Listen for mouse/keyboard events. goog.events.listen(menu, goog.ui.Component.EventType.ACTION, callback); // Listen for touch events (why doesn't Closure handle this already?). var callbackTouchStart = new Action <goog.events.BrowserEvent>((e) => { var control = menu.getOwnerControl((Node)e.target); // Highlight the menu item. control.handleMouseDown(e); }); var callbackTouchEnd = new Action <goog.events.Event>((e) => { var control = menu.getOwnerControl((Node)e.target); // Activate the menu item. control.performActionInternal(e); }); menu.getHandler().listen(menu.getElement(), goog.events.EventType.TOUCHSTART, callbackTouchStart); menu.getHandler().listen(menu.getElement(), goog.events.EventType.TOUCHEND, callbackTouchEnd); // Record windowSize and scrollOffset before adding menu. var windowSize = goog.dom.getViewportSize(); var scrollOffset = goog.style.getViewportPageOffset(Document.Instance); var xy = this.getAbsoluteXY_(); var borderBBox = this.getScaledBBox_(); var div = WidgetDiv.DIV; menu.render(div); var menuDom = menu.getElement(); Core.addClass_(menuDom, "blocklyDropdownMenu"); // Record menuSize after adding menu. var menuSize = goog.style.getSize(menuDom); // Recalculate height for the total content, not only box height. menuSize.height = menuDom.ScrollHeight; // Position the menu. // Flip menu vertically if off the bottom. if (xy.y + menuSize.height + borderBBox.height >= windowSize.height + scrollOffset.y) { xy.y -= menuSize.height + 2; } else { xy.y += borderBBox.height; } if (this.sourceBlock_.RTL) { xy.x += borderBBox.width; xy.x += FieldDropdown.CHECKMARK_OVERHANG; // Don't go offscreen left. if (xy.x < scrollOffset.x + menuSize.width) { xy.x = scrollOffset.x + menuSize.width; } } else { xy.x -= FieldDropdown.CHECKMARK_OVERHANG; // Don't go offscreen right. if (xy.x > windowSize.width + scrollOffset.x - menuSize.width) { xy.x = windowSize.width + scrollOffset.x - menuSize.width; } } WidgetDiv.position(xy.x, xy.y, windowSize, scrollOffset, this.sourceBlock_.RTL); menu.setAllowAutoFocus(true); menuDom.Focus(); }
/// <summary> /// Construct the menu based on the list of options and show the menu. /// </summary> /// <param name="e">Mouse event.</param> /// <param name="options">Array of menu options.</param> /// <param name="rtl">True if RTL, false if LTR.</param> public static void show(MouseEvent e, ContextMenuOption[] options, bool rtl) { WidgetDiv.show(Core.ContextMenu, rtl, null); if (options.Length == 0) { ContextMenu.hide(); return; } /* Here's what one option object looks like: * {text: 'Make It So', * enabled: true, * callback: Blockly.MakeItSo} */ var menu = new goog.ui.Menu(); menu.setRightToLeft(rtl); foreach (var option in options) { var menuItem = new goog.ui.MenuItem(option.text); menuItem.setRightToLeft(rtl); menu.addChild(menuItem, true); menuItem.setEnabled(option.enabled); if (option.enabled) { goog.events.listen(menuItem, goog.ui.Component.EventType.ACTION, option.callback); } } goog.events.listen(menu, goog.ui.Component.EventType.ACTION, new Action <goog.events.Event>((ev) => ContextMenu.hide())); // Record windowSize and scrollOffset before adding menu. var windowSize = goog.dom.getViewportSize(); var scrollOffset = goog.style.getViewportPageOffset(Document.Instance); var div = WidgetDiv.DIV; menu.render(div); var menuDom = menu.getElement(); Core.addClass_(menuDom, "blocklyContextMenu"); // Prevent system context menu when right-clicking a Blockly context menu. Core.bindEventWithChecks_(menuDom, "contextmenu", null, new Action <Event>(Core.noEvent)); // Record menuSize after adding menu. var menuSize = goog.style.getSize(menuDom); // Position the menu. var x = e.ClientX + scrollOffset.x; var y = e.ClientY + scrollOffset.y; // Flip menu vertically if off the bottom. if (e.ClientY + menuSize.height >= windowSize.height) { y -= menuSize.height; } // Flip menu horizontally if off the edge. if (rtl) { if (menuSize.width >= e.ClientX) { x += menuSize.width; } } else { if (e.ClientX + menuSize.width >= windowSize.width) { x -= menuSize.width; } } WidgetDiv.position(x, y, windowSize, scrollOffset, rtl); menu.setAllowAutoFocus(true); // 1ms delay is required for focusing on context menus because some other // mouse event is still waiting in the queue and clears focus. Window.SetTimeout(() => { menuDom.Focus(); }, 1); ContextMenu.currentBlock = null; // May be set by Blockly.Block. }