Add support for grouping scene operations into a drop menu button

- Issue MatterHackers/MCCentral#5664
consider adding a dual align quick button
This commit is contained in:
John Lewin 2019-06-13 08:16:50 -07:00
parent 3a6868f39a
commit cb6eb43972
6 changed files with 229 additions and 114 deletions

View file

@ -783,53 +783,61 @@ namespace MatterHackers.MatterControl
Icon = (invertIcon) => AggContext.StaticData.LoadIcon("lay_flat.png", 16, 16).SetPreMultiply(),
},
new SceneSelectionSeparator(),
new SceneSelectionOperation()
new OperationGroup()
{
OperationType = typeof(CombineObject3D_2),
TitleResolver = () => "Combine".Localize(),
Action = (sceneContext) => new CombineObject3D_2().WrapSelectedItemAndSelect(sceneContext.Scene),
Icon = (invertIcon) => AggContext.StaticData.LoadIcon("combine.png").SetPreMultiply(),
IsEnabled = (scene) =>
StickySelection = true,
IsEnabled = (scene) => scene.SelectedItem?.VisibleMeshes().Count() > 1,
Operations = new List<SceneSelectionOperation>()
{
var selectedItem = scene.SelectedItem;
return selectedItem != null && selectedItem.VisibleMeshes().Count() > 1;
},
},
new SceneSelectionOperation()
{
OperationType = typeof(SubtractObject3D_2),
TitleResolver = () => "Subtract".Localize(),
Action = (sceneContext) => new SubtractObject3D_2().WrapSelectedItemAndSelect(sceneContext.Scene),
Icon = (invertIcon) => AggContext.StaticData.LoadIcon("subtract.png").SetPreMultiply(),
IsEnabled = (scene) =>
{
var selectedItem = scene.SelectedItem;
return selectedItem != null && selectedItem.VisibleMeshes().Count() > 1;
},
},
new SceneSelectionOperation()
{
OperationType = typeof(IntersectionObject3D_2),
TitleResolver = () => "Intersect".Localize(),
Action = (sceneContext) => new IntersectionObject3D_2().WrapSelectedItemAndSelect(sceneContext.Scene),
Icon = (invertIcon) => AggContext.StaticData.LoadIcon("intersect.png"),
IsEnabled = (scene) =>
{
var selectedItem = scene.SelectedItem;
return selectedItem != null && selectedItem.VisibleMeshes().Count() > 1;
},
},
new SceneSelectionOperation()
{
OperationType = typeof(SubtractAndReplaceObject3D_2),
TitleResolver = () => "Subtract & Replace".Localize(),
Action = (sceneContext) => new SubtractAndReplaceObject3D_2().WrapSelectedItemAndSelect(sceneContext.Scene),
Icon = (invertIcon) => AggContext.StaticData.LoadIcon("subtract_and_replace.png").SetPreMultiply(),
IsEnabled = (scene) =>
{
var selectedItem = scene.SelectedItem;
return selectedItem != null && selectedItem.VisibleMeshes().Count() > 1;
},
new SceneSelectionOperation()
{
OperationType = typeof(CombineObject3D_2),
TitleResolver = () => "Combine".Localize(),
Action = (sceneContext) => new CombineObject3D_2().WrapSelectedItemAndSelect(sceneContext.Scene),
Icon = (invertIcon) => AggContext.StaticData.LoadIcon("combine.png").SetPreMultiply(),
IsEnabled = (scene) =>
{
var selectedItem = scene.SelectedItem;
return selectedItem != null && selectedItem.VisibleMeshes().Count() > 1;
},
},
new SceneSelectionOperation()
{
OperationType = typeof(SubtractObject3D_2),
TitleResolver = () => "Subtract".Localize(),
Action = (sceneContext) => new SubtractObject3D_2().WrapSelectedItemAndSelect(sceneContext.Scene),
Icon = (invertIcon) => AggContext.StaticData.LoadIcon("subtract.png").SetPreMultiply(),
IsEnabled = (scene) =>
{
var selectedItem = scene.SelectedItem;
return selectedItem != null && scene.SelectedItem.VisibleMeshes().Count() > 1;
},
},
new SceneSelectionOperation()
{
OperationType = typeof(IntersectionObject3D_2),
TitleResolver = () => "Intersect".Localize(),
Action = (sceneContext) => new IntersectionObject3D_2().WrapSelectedItemAndSelect(sceneContext.Scene),
Icon = (invertIcon) => AggContext.StaticData.LoadIcon("intersect.png"),
IsEnabled = (scene) =>
{
var selectedItem = scene.SelectedItem;
return selectedItem != null && selectedItem.VisibleMeshes().Count() > 1;
},
},
new SceneSelectionOperation()
{
OperationType = typeof(SubtractAndReplaceObject3D_2),
TitleResolver = () => "Subtract & Replace".Localize(),
Action = (sceneContext) => new SubtractAndReplaceObject3D_2().WrapSelectedItemAndSelect(sceneContext.Scene),
Icon = (invertIcon) => AggContext.StaticData.LoadIcon("subtract_and_replace.png").SetPreMultiply(),
IsEnabled = (scene) =>
{
var selectedItem = scene.SelectedItem;
return selectedItem != null && selectedItem.VisibleMeshes().Count() > 1;
},
}
}
},
new SceneSelectionSeparator(),
new SceneSelectionOperation()
@ -1176,6 +1184,7 @@ namespace MatterHackers.MatterControl
}
static int applicationInstanceCount = 0;
public static int ApplicationInstanceCount
{
get

View file

@ -410,6 +410,45 @@ namespace MatterHackers.MatterControl
return popupMenu;
}
public PopupMenuButton CreateSplitButton(SplitButtonParams buttonParams)
{
PopupMenuButton menuButton = null;
var innerButton = new IconButton(buttonParams.Icon, this)
{
Name = buttonParams.ButtonName + " Inner SplitButton",
ToolTipText = buttonParams.DefaultActionTooltip,
};
innerButton.Click += (s, e) =>
{
buttonParams.DefaultAction.Invoke(menuButton);
};
// Remove right Padding for drop style
innerButton.Padding = innerButton.Padding.Clone(right: 0);
menuButton = new PopupMenuButton(innerButton, this)
{
DynamicPopupContent = () =>
{
var popupMenu = new PopupMenu(ApplicationController.Instance.MenuTheme);
buttonParams.ExtendPopupMenu?.Invoke(popupMenu);
return popupMenu;
},
Name = buttonParams.ButtonName + " Menu SplitButton",
BackgroundColor = this.ToolbarButtonBackground,
HoverColor = this.ToolbarButtonHover,
MouseDownColor = this.ToolbarButtonDown,
DrawArrow = true,
Margin = this.ButtonSpacing,
};
innerButton.Selectable = true;
return menuButton;
}
private static ImageBuffer ColorCircle(int size, Color color)
{
ImageBuffer imageBuffer = new ImageBuffer(size, size);
@ -546,15 +585,35 @@ namespace MatterHackers.MatterControl
public class PresetColors
{
public Color MaterialPreset { get; set; } = Color.Orange;
public Color QualityPreset { get; set; } = Color.Yellow;
public Color UserOverride { get; set; } = new Color(68, 95, 220, 150);
}
public class GridColors
{
public Color Red { get; set; }
public Color Green { get; set; }
public Color Blue { get; set; }
public Color Line { get; set; }
}
public class SplitButtonParams
{
public ImageBuffer Icon { get; set; }
public Action<GuiWidget> DefaultAction { get; set; }
public string DefaultActionTooltip { get; set; }
public Action MenuAction { get; set; }
public Action<PopupMenu> ExtendPopupMenu { get; set; }
public string ButtonName { get; set; }
}
}

View file

@ -49,9 +49,23 @@ namespace MatterHackers.Agg.UI
public Func<string> TitleResolver { get; set; }
public string Title => this.TitleResolver?.Invoke();
public Func<string> HelpTextResolver { get; set; }
public string HelpText => this.HelpTextResolver?.Invoke();
}
public class SceneSelectionSeparator : SceneSelectionOperation
{
}
public class OperationGroup : SceneSelectionOperation
{
public List<SceneSelectionOperation> Operations { get; set; } = new List<SceneSelectionOperation>();
public bool StickySelection { get; internal set; }
public string GroupName { get; set; }
}
}

View file

@ -252,14 +252,11 @@ namespace MatterHackers.MatterControl.CustomWidgets
public ImageBuffer IconImage => this.Enabled ? image : this.DisabledImage;
/// <summary>
/// Switch icons without computing disabled image - use case for non-disableable toggle widgets
/// </summary>
/// <param name="icon"></param>
internal void SetIcon(ImageBuffer icon)
{
image = icon;
imageWidget.Image = icon;
_disabledImage = null;
}
private ImageBuffer _disabledImage;

View file

@ -68,6 +68,7 @@ namespace MatterHackers.MatterControl.PartPreviewWindow
public class ViewModeChangedEventArgs : EventArgs
{
public PartViewMode ViewMode { get; set; }
public PartViewMode PreviousMode { get; set; }
}
@ -454,7 +455,67 @@ namespace MatterHackers.MatterControl.PartPreviewWindow
GuiWidget button;
if (namedAction.Icon != null)
if (namedAction is OperationGroup operationGroup)
{
SceneSelectionOperation defaultOperation;
string groupRecordID = $"ActiveButton_{operationGroup.GroupName}_Group";
if (operationGroup.StickySelection)
{
int.TryParse(UserSettings.Instance.get(groupRecordID), out int activeButtonID);
activeButtonID = agg_basics.Clamp(activeButtonID, 0, operationGroup.Operations.Count - 1);
defaultOperation = operationGroup.Operations[activeButtonID];
}
else
{
defaultOperation = operationGroup.Operations.First();
}
PopupMenuButton groupButton = null;
groupButton = theme.CreateSplitButton(new SplitButtonParams()
{
Icon = defaultOperation.Icon(theme.InvertIcons),
DefaultAction = (menuButton) =>
{
defaultOperation.Action.Invoke(sceneContext);
},
DefaultActionTooltip = defaultOperation.HelpText ?? defaultOperation.Title,
ButtonName = defaultOperation.Title,
ExtendPopupMenu = (PopupMenu popupMenu) =>
{
foreach (var operation in operationGroup.Operations)
{
var operationMenu = popupMenu.CreateMenuItem(operation.Title, operation.Icon?.Invoke(theme.InvertIcons));
operationMenu.Click += (s, e) => UiThread.RunOnIdle(() =>
{
if (operationGroup.StickySelection
&& defaultOperation != operation)
{
// Update button
var iconButton = groupButton.Children.OfType<IconButton>().First();
iconButton.SetIcon(operation.Icon(theme.InvertIcons));
iconButton.ToolTipText = operation.HelpText ?? operation.Title;
UserSettings.Instance.set(groupRecordID, operationGroup.Operations.IndexOf(operation).ToString());
defaultOperation = operation;
iconButton.Invalidate();
}
operation.Action?.Invoke(sceneContext);
});
}
}
});
button = groupButton;
}
else if (namedAction.Icon != null)
{
// add the create support before the align
if (namedAction.OperationType == typeof(AlignObject3D))
@ -487,16 +548,18 @@ namespace MatterHackers.MatterControl.PartPreviewWindow
operationButtons.Add((button, namedAction));
button.Click += (s, e) =>
// Only bind Click event if not a SplitButton
if (!(button is PopupMenuButton))
{
UiThread.RunOnIdle(() =>
button.Click += (s, e) => UiThread.RunOnIdle(() =>
{
namedAction.Action.Invoke(sceneContext);
var partTab = button.Parents<PartTabPage>().FirstOrDefault();
var view3D = partTab.Descendants<View3DWidget>().FirstOrDefault();
view3D.InteractionLayer.Focus();
});
};
}
this.AddChild(button);
}
@ -776,83 +839,56 @@ namespace MatterHackers.MatterControl.PartPreviewWindow
private GuiWidget CreateSaveButton(ThemeConfig theme)
{
PopupMenuButton saveButton = null;
var iconButton = new IconButton(
AggContext.StaticData.LoadIcon("save_grey_16x.png", 16, 16, theme.InvertIcons),
theme)
return theme.CreateSplitButton(new SplitButtonParams()
{
ToolTipText = "Save".Localize(),
};
iconButton.Click += (s, e) =>
{
ApplicationController.Instance.Tasks.Execute("Saving".Localize(), sceneContext.Printer, async(progress, cancellationToken) =>
ButtonName = "Save",
Icon = AggContext.StaticData.LoadIcon("save_grey_16x.png", 16, 16, theme.InvertIcons),
DefaultAction = (menuButton) =>
{
saveButton.Enabled = false;
try
ApplicationController.Instance.Tasks.Execute("Saving".Localize(), sceneContext.Printer, async (progress, cancellationToken) =>
{
await sceneContext.SaveChanges(progress, cancellationToken);
}
catch
{
}
menuButton.Enabled = false;
saveButton.Enabled = true;
}).ConfigureAwait(false);
};
try
{
await sceneContext.SaveChanges(progress, cancellationToken);
}
catch (Exception ex)
{
ApplicationController.Instance.LogError("Error saving file".Localize() + ": " + ex.Message);
}
// Remove right Padding for drop style
iconButton.Padding = iconButton.Padding.Clone(right: 0);
saveButton = new PopupMenuButton(iconButton, theme)
{
Name = "Save SplitButton",
ToolTipText = "Save As".Localize(),
DynamicPopupContent = () =>
menuButton.Enabled = true;
}).ConfigureAwait(false);
},
DefaultActionTooltip = "Save".Localize(),
ExtendPopupMenu = (PopupMenu popupMenu) =>
{
var popupMenu = new PopupMenu(ApplicationController.Instance.MenuTheme);
var saveAs = popupMenu.CreateMenuItem("Save As".Localize());
saveAs.Click += (s, e) => UiThread.RunOnIdle(() =>
{
UiThread.RunOnIdle(() =>
{
DialogWindow.Show(
new SaveAsPage(
async (newName, destinationContainer) =>
DialogWindow.Show(
new SaveAsPage(
(newName, destinationContainer) =>
{
// Save to the destination provider
if (destinationContainer is ILibraryWritableContainer writableContainer)
{
// Save to the destination provider
if (destinationContainer is ILibraryWritableContainer writableContainer)
// Wrap stream with ReadOnlyStream library item and add to container
writableContainer.Add(new[]
{
// Wrap stream with ReadOnlyStream library item and add to container
writableContainer.Add(new[]
{
new InMemoryLibraryItem(sceneContext.Scene)
{
Name = newName
}
});
});
destinationContainer.Dispose();
}
}));
});
destinationContainer.Dispose();
}
}));
});
return popupMenu;
},
BackgroundColor = theme.ToolbarButtonBackground,
HoverColor = theme.ToolbarButtonHover,
MouseDownColor = theme.ToolbarButtonDown,
DrawArrow = true,
Margin = theme.ButtonSpacing,
};
iconButton.Selectable = true;
return saveButton;
}
});
}
public override void OnClosed(EventArgs e)

View file

@ -686,7 +686,7 @@ namespace MatterHackers.MatterControl.Tests.Automation
public static void SaveBedplateToFolder(this AutomationRunner testRunner, string newFileName, string folderName)
{
testRunner.ClickByName("Save SplitButton", offset: new Point2D(8, 0));
testRunner.ClickByName("Save Menu SplitButton", offset: new Point2D(8, 0));
testRunner.ClickByName("Save As Menu Item");