popup menus; popup menu for editors

This commit is contained in:
Vadim Lopatin 2014-05-12 13:28:41 +04:00
parent 0b35db01c7
commit 6d518ed5c0
7 changed files with 105 additions and 19 deletions

View File

@ -93,7 +93,7 @@ extern (C) int UIAppMain(string[] args) {
mainMenuItems.add(windowItem);
mainMenuItems.add(helpItem);
MainMenu mainMenu = new MainMenu(mainMenuItems);
mainMenu.onMenuItemListener = delegate(MenuItem item) {
mainMenu.onMenuItemClickListener = delegate(MenuItem item) {
Log.d("mainMenu.onMenuItemListener", item.label);
const Action a = item.action;
if (a) {

View File

@ -96,7 +96,12 @@ struct Collection(T, bool ownItems = false) {
}
_items[index] = item;
_len++;
}
}
/// add all items from other collection
void addAll(ref Collection!(T, ownItems) v) {
for (int i = 0; i < v.length; i++)
add(v[i]);
}
/// support for appending (~=, +=) and removing by value (-=)
ref Collection opOpAssign(string op)(T item) {
static if (op.equal("~") || op.equal("+")) {

View File

@ -169,8 +169,13 @@ struct Signal(T1) if (is(T1 == interface) && __traits(allMembers, T1).length ==
alias return_t = ReturnType!(__traits(getMember, T1, __traits(allMembers, T1)[0]));
alias params_t = ParameterTypeTuple!(__traits(getMember, T1, __traits(allMembers, T1)[0]));
alias slot_t = return_t delegate(params_t);
private Collection!slot_t _listeners;
/// returns true if listener is assigned
private Collection!slot_t _listeners;
this(ref Signal!T1 v) {
_listeners.addAll(v._listeners);
}
/// returns true if listener is assigned
final bool assigned() {
return _listeners.length > 0;
}

View File

@ -805,7 +805,7 @@ class EditableContent {
}
/// base for all editor widgets
class EditWidgetBase : WidgetGroup, EditableContentListener {
class EditWidgetBase : WidgetGroup, EditableContentListener, MenuItemActionHandler {
protected EditableContent _content;
protected Rect _clientRc;
@ -905,6 +905,12 @@ class EditWidgetBase : WidgetGroup, EditableContentListener {
_popupMenu = popupMenu;
return this;
}
///
override bool onMenuItemAction(const Action action) {
return handleAction(action);
}
/// returns true if widget can show popup (e.g. by mouse right click at point x,y)
override bool canShowPopupMenu(int x, int y) {
if (_popupMenu is null)
@ -914,13 +920,38 @@ class EditWidgetBase : WidgetGroup, EditableContentListener {
return false;
return true;
}
/// override to change popup menu items state
override bool isActionEnabled(const Action action) {
switch (action.id) {
case EditorActions.Copy:
case EditorActions.Cut:
return !_selectionRange.empty;
case EditorActions.Paste:
return Platform.instance.getClipboardText().length > 0;
case EditorActions.Undo:
return _content.hasUndo;
case EditorActions.Redo:
return _content.hasRedo;
default:
return super.isActionEnabled(action);
}
}
/// shows popup at (x,y)
override void showPopupMenu(int x, int y) {
/// if preparation signal handler assigned, call it; don't show popup if false is returned from handler
if (_popupMenu.onBeforeOpeningSubmenu.assigned)
if (!_popupMenu.onBeforeOpeningSubmenu(_popupMenu))
return;
for (int i = 0; i < _popupMenu.subitemCount; i++) {
MenuItem item = _popupMenu.subitem(i);
if (item.action && isActionEnabled(item.action)) {
item.enabled = true;
} else {
item.enabled = false;
}
}
PopupMenu popupMenu = new PopupMenu(_popupMenu);
popupMenu.onMenuItemActionListener = this;
PopupWidget popup = window.showPopup(popupMenu, this, PopupAlign.Point | PopupAlign.Right, x, y);
popup.flags = PopupFlags.CloseOnClickOutside;
}

View File

@ -98,6 +98,14 @@ class MenuItem {
/// sets item action
@property MenuItem action(Action a) { _action = a; return this; }
/// menu item Enabled flag
@property bool enabled() { return _enabled; }
/// menu item Enabled flag
@property MenuItem enabled(bool enabled) {
_enabled = enabled;
return this;
}
/// handle menu item click
Signal!(void, MenuItem) onMenuItem;
/// prepare for opening of submenu, return true if opening is allowed
@ -218,16 +226,20 @@ class MenuItemWidget : WidgetGroup {
_mainMenu = mainMenu;
_item = item;
styleId = "MENU_ITEM";
if (!item.enabled)
resetState(State.Enabled);
// icon
if (_item.action && _item.action.iconId.length) {
_icon = new ImageWidget("MENU_ICON", _item.action.iconId);
_icon.styleId = "MENU_ICON";
_icon.state = State.Parent;
addChild(_icon);
}
// label
_label = new TextWidget("MENU_LABEL");
_label.text = _item.label;
_label.styleId = _mainMenu ? "MAIN_MENU_LABEL" : "MENU_LABEL";
_label.state = State.Parent;
addChild(_label);
// accelerator
dstring acc = _item.acceleratorText;
@ -235,6 +247,7 @@ class MenuItemWidget : WidgetGroup {
_accel = new TextWidget("MENU_ACCEL");
_accel.styleId = "MENU_ACCEL";
_accel.text = acc;
_accel.state = State.Parent;
addChild(_accel);
}
trackHover = true;
@ -242,6 +255,14 @@ class MenuItemWidget : WidgetGroup {
}
}
interface MenuItemClickHandler {
bool onMenuItemClick(MenuItem item);
}
interface MenuItemActionHandler {
bool onMenuItemAction(const Action action);
}
/// base class for menus
class MenuWidgetBase : ListWidget {
protected MenuWidgetBase _parentMenu;
@ -249,11 +270,11 @@ class MenuWidgetBase : ListWidget {
protected PopupMenu _openedMenu;
protected PopupWidget _openedPopup;
protected int _openedPopupIndex;
protected bool delegate(MenuItem item) _onMenuItemClickListener;
/// menu item click listener
@property bool delegate(MenuItem item) onMenuItemListener() { return _onMenuItemClickListener; }
/// menu item click listener
@property MenuWidgetBase onMenuItemListener(bool delegate(MenuItem item) listener) { _onMenuItemClickListener = listener; return this; }
/// menu item click listener
Signal!MenuItemClickHandler onMenuItemClickListener;
/// menu item action listener
Signal!MenuItemActionHandler onMenuItemActionListener;
this(MenuWidgetBase parentMenu, MenuItem item, Orientation orientation) {
_parentMenu = parentMenu;
@ -392,13 +413,22 @@ class MenuWidgetBase : ListWidget {
selectItem(-1);
setHoverItem(-1);
selectOnHover = false;
bool delegate(MenuItem item) listener = _onMenuItemClickListener;
// copy menu item click listeners
Signal!MenuItemClickHandler onMenuItemClickListenerCopy = onMenuItemClickListener;
// copy item action listeners
Signal!MenuItemActionHandler onMenuItemActionListenerCopy = onMenuItemActionListener;
PopupWidget popup = cast(PopupWidget)parent;
if (popup)
popup.close();
// this pointer now can be invalid - if popup removed
if (listener !is null)
listener(item);
if (onMenuItemClickListenerCopy.assigned)
if (onMenuItemClickListenerCopy(item))
return;
// this pointer now can be invalid - if popup removed
if (onMenuItemActionListenerCopy.assigned)
onMenuItemActionListenerCopy(item.action);
}
}
@ -526,10 +556,21 @@ class MainMenu : MenuWidgetBase {
override protected void onMenuItem(MenuItem item) {
debug Log.d("MainMenu.onMenuItem ", item.action.label);
bool delegate(MenuItem item) listener = _onMenuItemClickListener;
// copy menu item click listeners
Signal!MenuItemClickHandler onMenuItemClickListenerCopy = onMenuItemClickListener;
// copy item action listeners
Signal!MenuItemActionHandler onMenuItemActionListenerCopy = onMenuItemActionListener;
deactivate();
if (listener !is null)
listener(item);
// this pointer now can be invalid - if popup removed
if (onMenuItemClickListenerCopy.assigned)
if (onMenuItemClickListenerCopy(item))
return;
// this pointer now can be invalid - if popup removed
if (onMenuItemActionListenerCopy.assigned)
onMenuItemActionListenerCopy(item.action);
}
/// return true if main menu is activated (focused or has open submenu)

View File

@ -757,9 +757,9 @@ Theme createDefaultTheme() {
menuItem.createState(State.Selected, State.Selected).backgroundColor(0x00F8F9Fa);
menuItem.createState(State.Hovered, State.Hovered).backgroundColor(0xC0FFFF00);
res.createSubstyle("MENU_ICON").setMargins(2,2,2,2).alignment(Align.VCenter|Align.Left);
res.createSubstyle("MENU_LABEL").setMargins(4,2,4,2).alignment(Align.VCenter|Align.Left).textFlags(TextFlag.UnderlineHotKeys);
res.createSubstyle("MAIN_MENU_LABEL").setMargins(4,2,4,2).alignment(Align.VCenter|Align.Left).textFlags(TEXT_FLAGS_USE_PARENT);
res.createSubstyle("MENU_ACCEL").setMargins(4,2,4,2).alignment(Align.VCenter|Align.Left);
res.createSubstyle("MENU_LABEL").setMargins(4,2,4,2).alignment(Align.VCenter|Align.Left).textFlags(TextFlag.UnderlineHotKeys).createState(State.Enabled,0).textColor(0x80404040);
res.createSubstyle("MAIN_MENU_LABEL").setMargins(4,2,4,2).alignment(Align.VCenter|Align.Left).textFlags(TEXT_FLAGS_USE_PARENT).createState(State.Enabled,0).textColor(0x80404040);
res.createSubstyle("MENU_ACCEL").setMargins(4,2,4,2).alignment(Align.VCenter|Align.Left).createState(State.Enabled,0).textColor(0x80404040);
Style transparentButtonBackground = res.createSubstyle("TRANSPARENT_BUTTON_BACKGROUND").backgroundImageId("transparent_button_background").setPadding(4,2,4,2); //.backgroundColor(0xE0E080) ;
//transparentButtonBackground.createState(State.Focused, State.Focused).backgroundColor(0xC0C0C000);

View File

@ -1050,6 +1050,10 @@ class Widget {
void showPopupMenu(int x, int y) {
// override to show popup
}
/// override to change popup menu items state
bool isActionEnabled(const Action action) {
return true;
}
// ===========================================================
// Widget hierarhy methods