haxe/ui/containers/menus/MenuBar.hx
package haxe.ui.containers.menus;
import haxe.ui.Toolkit;
import haxe.ui.behaviours.DefaultBehaviour;
import haxe.ui.components.Button;
import haxe.ui.containers.HBox;
import haxe.ui.containers.menus.Menu.MenuEvents;
import haxe.ui.containers.menus.Menu;
import haxe.ui.core.Component;
import haxe.ui.core.CompositeBuilder;
import haxe.ui.core.Screen;
import haxe.ui.events.Events;
import haxe.ui.events.MenuEvent;
import haxe.ui.events.MouseEvent;
import haxe.ui.events.UIEvent;
import haxe.ui.locale.LocaleManager;
#if (haxe_ver >= 4.2)
import Std.isOfType;
#else
import Std.is as isOfType;
#end
@:composite(Events, Builder)
class MenuBar extends HBox {
@:behaviour(DefaultBehaviour) public var menuStyleNames:String;
/**
Utility property to add a single `MenuEvent.MENU_SELECTED` event
**/
@:event(MenuEvent.MENU_SELECTED) public var onMenuSelected:MenuEvent->Void;
@:event(MenuEvent.MENU_OPENED) public var onMenuOpened:MenuEvent->Void;
@:event(MenuEvent.MENU_CLOSED) public var onMenuClosed:MenuEvent->Void;
private override function onThemeChanged() {
super.onThemeChanged();
var builder:Builder = cast(this._compositeBuilder, Builder);
builder.onThemeChanged();
}
}
//***********************************************************************************************************
// Behaviours
//***********************************************************************************************************
//***********************************************************************************************************
// Events
//***********************************************************************************************************
@:dox(hide) @:noCompletion
@:access(haxe.ui.core.Component)
@:access(haxe.ui.containers.menus.Builder)
private class Events extends haxe.ui.events.Events {
private var _menubar:MenuBar;
private var _currentMenu:Menu;
private var _currentButton:Button;
public function new(menubar:MenuBar) {
super(menubar);
_menubar = menubar;
}
public override function register() {
var builder:Builder = cast(_menubar._compositeBuilder, Builder);
for (button in builder._buttons) {
if (!button.hasEvent(MouseEvent.CLICK, onButtonClick)) {
button.registerEvent(MouseEvent.CLICK, onButtonClick);
}
if (!button.hasEvent(MouseEvent.MOUSE_OVER, onButtonOver)) {
button.registerEvent(MouseEvent.MOUSE_OVER, onButtonOver);
}
}
}
public override function unregister() {
var builder:Builder = cast(_menubar._compositeBuilder, Builder);
for (button in builder._buttons) {
button.unregisterEvent(MouseEvent.CLICK, onButtonClick);
button.unregisterEvent(MouseEvent.MOUSE_OVER, onButtonOver);
}
}
private function onCompleteButton(event:MouseEvent) {
var target:Button = cast(event.target, Button);
target.unregisterEvent(MouseEvent.MOUSE_OUT, onCompleteButton);
hideCurrentMenu();
}
private function onButtonClick(event:MouseEvent) {
var builder:Builder = cast(_menubar._compositeBuilder, Builder);
var target:Button = cast(event.target, Button);
var index = builder._buttons.indexOf(target);
if (target.toggle == false) {
var menu = builder._menus[index];
var newEvent = new MenuEvent(MenuEvent.MENU_SELECTED);
newEvent.menu = menu;
_menubar.dispatch(newEvent);
target.registerEvent(MouseEvent.MOUSE_OUT, onCompleteButton);
menu.dispatch(new MouseEvent(MouseEvent.CLICK));
return;
}
if (target.selected == true) {
showMenu(index);
} else if (_currentButton != null) {
hideCurrentMenu();
}
}
private function onButtonOver(event:MouseEvent) {
if (_currentMenu == null) {
return;
}
var builder:Builder = cast(_menubar._compositeBuilder, Builder);
var target:Button = cast(event.target, Button);
var index = builder._buttons.indexOf(target);
var menu = builder._menus[index];
if (menu != _currentMenu) {
showMenu(index, true);
}
}
private function showMenu(index:Int, force:Bool = false) {
var builder:Builder = cast(_menubar._compositeBuilder, Builder);
var menu:Menu = builder._menus[index];
menu.addClass("primary-menu");
var hasChildren = (menu.childComponents.length > 0);
var target:Button = builder._buttons[index];
if (_currentMenu == menu) {
return;
}
menu.menuBar = _menubar;
for (button in builder._buttons) {
if (button != target) {
button.selected = false;
}
}
target.selected = true;
hideCurrentMenu(force);
cast(menu._internalEvents, MenuEvents).button = target;
if (menu.hasEvent(UIEvent.CLOSE, onMenuClose) == false) {
menu.registerEvent(UIEvent.CLOSE, onMenuClose);
}
var rtl = false;
if (hasChildren == true) {
var componentOffset = target.getComponentOffset();
var left = target.screenLeft + componentOffset.x;
var marginTop:Float = 0;
if (menu.style != null && menu.style.marginTop != null) {
marginTop = menu.style.marginTop;
}
var top = target.screenTop + (target.actualComponentHeight - Toolkit.scaleY) + componentOffset.y + marginTop;
menu.menuStyleNames = _menubar.menuStyleNames;
menu.addClasses([_menubar.menuStyleNames, "expanded"]);
if (menu.findComponent("menu-filler", false) == null) {
var filler = new Component();
filler.horizontalAlign = "right";
filler.includeInLayout = false;
filler.addClass("menu-filler");
filler.id = "menu-filler";
menu.addComponent(filler);
}
menu.pauseEvent(MouseEvent.MOUSE_OVER, true);
menu.show();
menu.syncComponentValidation();
menu.resumeEvent(MouseEvent.MOUSE_OVER, true, true);
if (left + menu.actualComponentWidth > Screen.instance.actualWidth) {
left = target.screenLeft - menu.actualComponentWidth + target.actualComponentWidth;
rtl = true;
}
menu.left = left;
menu.top = top - Toolkit.scaleY;
}
_currentButton = target;
_currentMenu = menu;
if (hasChildren == true) {
var cx = menu.width - _currentButton.width;
var offset:Float = 0;
if (_currentMenu.style != null && _currentMenu.style.borderRadiusTopRight > 0) {
offset = _currentMenu.style.borderRadiusTopRight - 1;
}
cx -= offset;
var filler:Component = menu.findComponent("menu-filler", false);
if (cx > 0 && filler != null) {
cx += 1;
filler.width = cx;
if (rtl == false) {
filler.left = menu.width - cx - offset;
}
} else if (filler != null) {
menu.removeComponent(filler);
}
if (!_currentMenu.hasEvent(MenuEvent.MENU_SELECTED, onMenuSelected)) {
_currentMenu.registerEvent(MenuEvent.MENU_SELECTED, onMenuSelected);
}
}
if (hasChildren == true) {
//_currentMenu.dispatch(new MouseEvent(MouseEvent.CLICK));
var menuEvent = new MenuEvent(MenuEvent.MENU_OPENED);
menuEvent.menu = _currentMenu;
_currentMenu.dispatch(menuEvent);
this.dispatch(menuEvent);
}
}
private function hideCurrentMenu(force:Bool = false) {
if (_currentMenu != null) {
var builder:Builder = cast(_menubar._compositeBuilder, Builder);
for (button in builder._buttons) {
if (button.hasClass("menubar-button-no-children-active")) {
button.swapClass("menubar-button-no-children", "menubar-button-no-children-active");
}
}
if (!force && _currentMenu.hitTest(Screen.instance.currentMouseX, Screen.instance.currentMouseY)) {
var beforeCloseEvent = new UIEvent(UIEvent.BEFORE_CLOSE);
beforeCloseEvent.relatedComponent = _currentMenu.findComponentsUnderPoint(Screen.instance.currentMouseX, Screen.instance.currentMouseY, MenuItem)[0];
_currentMenu.dispatch(beforeCloseEvent);
if (beforeCloseEvent.canceled) {
return;
}
}
if (!force && _currentMenu.hitTest(Screen.instance.currentMouseX, Screen.instance.currentMouseY)) {
var beforeCloseEvent = new UIEvent(UIEvent.BEFORE_CLOSE);
beforeCloseEvent.relatedComponent = _currentMenu.findComponentsUnderPoint(Screen.instance.currentMouseX, Screen.instance.currentMouseY, MenuItem)[0];
_menubar.dispatch(beforeCloseEvent);
if (beforeCloseEvent.canceled) {
return;
}
}
var menuEvent = new MenuEvent(MenuEvent.MENU_CLOSED);
menuEvent.menu = _currentMenu;
_currentMenu.dispatch(menuEvent);
this.dispatch(menuEvent);
_currentMenu.unregisterEvent(MenuEvent.MENU_SELECTED, onMenuSelected);
_currentMenu.hide();
_currentButton.selected = false;
_currentButton = null;
_currentMenu = null;
}
}
private function onMenuClose(event:UIEvent) {
var menu = cast(event.target, Menu);
if (_currentMenu == menu) {
hideCurrentMenu();
}
}
private function onMenuSelected(event:MenuEvent) {
var newEvent = new MenuEvent(MenuEvent.MENU_SELECTED);
newEvent.menu = event.menu;
newEvent.menuItem = event.menuItem;
_menubar.dispatch(newEvent);
hideCurrentMenu();
}
}
//***********************************************************************************************************
// Composite Builder
//***********************************************************************************************************
@:dox(hide) @:noCompletion
@:access(haxe.ui.core.Component)
private class Builder extends CompositeBuilder {
private var _menubar:MenuBar;
private var _buttons:Array<Button> = [];
private var _menus:Array<Menu> = [];
public function new(menubar:MenuBar) {
super(menubar);
_menubar = menubar;
}
@:access(haxe.ui.core.Screen)
public function onThemeChanged() {
for (menu in _menus) {
Screen.instance.invalidateChildren(menu);
Screen.instance.onThemeChangedChildren(menu);
}
}
public override function create() {
}
public override function destroy() {
super.destroy();
for (menu in _menus) {
Screen.instance.removeComponent(menu);
}
_menus = null;
}
public override function addComponent(child:Component):Component {
if ((child is Menu)) {
var menu = cast(child, Menu);
var button = new Button();
button.text = menu.text;
button.icon = menu.icon;
button.tooltip = menu.tooltip;
button.hidden = child.hidden;
button.allowFocus = false;
button.disabled = child.disabled;
LocaleManager.instance.cloneForComponent(child, button);
_buttons.push(button);
_menubar.addComponent(button);
_menubar.registerInternalEvents(true);
_menus.push(menu);
styleMenuButton(menu, button);
menu.registerEvent(UIEvent.COMPONENT_ADDED, onChildAdded);
menu.registerEvent(UIEvent.COMPONENT_REMOVED, onChildRemoved);
menu.registerEvent(UIEvent.PROPERTY_CHANGE, onMenuPropertyChanged);
return menu;
}
return null;
}
private function onChildAdded(event:UIEvent) {
var menu = cast(event.target, Menu);
var index = _menus.indexOf(menu);
var button = _buttons[index];
styleMenuButton(menu, button);
}
private function onChildRemoved(event:UIEvent) {
var menu = cast(event.target, Menu);
var index = _menus.indexOf(menu);
var button = _buttons[index];
styleMenuButton(menu, button);
}
private function onMenuPropertyChanged(event:UIEvent) {
if (event.data == "text") {
var menu = cast(event.target, Menu);
var index = _menus.indexOf(menu);
var button = _buttons[index];
button.text = event.target.text;
} else if (event.data == "hidden") {
var menu = cast(event.target, Menu);
var index = _menus.indexOf(menu);
var button = _buttons[index];
button.hidden = event.target.hidden;
} else if (event.data == "disabled") {
var menu = cast(event.target, Menu);
var index = _menus.indexOf(menu);
var button = _buttons[index];
button.disabled = event.target.disabled;
} else if (event.data == "icon") {
var menu = cast(event.target, Menu);
var index = _menus.indexOf(menu);
var button = _buttons[index];
button.icon = menu.icon;
}
}
public override function addComponentAt(child:Component, index:Int):Component {
return null;
}
public override function removeComponent(child:Component, dispose:Bool = true, invalidate:Bool = true):Component {
return null;
}
private function styleMenuButton(menu:Menu, button:Button) {
var hasChildren = (menu.childComponents.length > 0);
if (hasChildren == true) {
button.swapClass("menubar-button", "menubar-button-no-children");
} else {
button.swapClass("menubar-button-no-children", "menubar-button");
}
button.toggle = hasChildren;
_menubar.registerInternalEvents(true);
}
public override function getComponentIndex(child:Component):Int {
return -1;
}
public override function setComponentIndex(child:Component, index:Int):Component {
return null;
}
public override function getComponentAt(index:Int):Component {
return null;
}
public override function findComponent<T:Component>(criteria:String, type:Class<T>, recursive:Null<Bool>, searchType:String):Null<T> {
var match = super.findComponent(criteria, type, recursive, searchType);
if (match == null) {
for (menu in _menus) {
match = menu.findComponent(criteria, type, recursive, searchType);
if (menu.matchesSearch(criteria, type, searchType)) {
return cast menu;
} else {
match = menu.findComponent(criteria, type, recursive, searchType);
}
if (match != null) {
break;
}
}
}
return cast match;
}
public override function findComponents<T:Component>(styleName:String = null, type:Class<T> = null, maxDepth:Int = 5):Array<T> {
var r:Array<T> = [];
for (menu in _menus) {
var match = true;
if (styleName != null && menu.hasClass(styleName) == false) {
match = false;
}
if (type != null && isOfType(menu, type) == false) {
match = false;
}
if (match == true) {
r.push(cast menu);
}
var childArray = menu.findComponents(styleName, type, maxDepth);
for (c in childArray) { // r.concat caused issues here on hxcpp
r.push(c);
}
}
return r;
}
}