1
0
mirror of https://github.com/Artemis-RGB/Artemis synced 2025-12-13 05:48:35 +00:00

Sidebar - Refactored category VM to fix all issues with it I could find

Sidebar - Streamlined design
Sidebar - Increase width on wide window sizes
Profile editor - Implement File and Help menus
This commit is contained in:
Robert 2022-05-14 22:57:53 +02:00
parent 35f83e58e5
commit 78479c0fa2
79 changed files with 1093 additions and 946 deletions

View File

@ -18,8 +18,11 @@
<entry key="Artemis.UI.Windows/App.axaml" value="Artemis.UI.Windows/Artemis.UI.Windows.csproj" /> <entry key="Artemis.UI.Windows/App.axaml" value="Artemis.UI.Windows/Artemis.UI.Windows.csproj" />
<entry key="Artemis.UI/DefaultTypes/PropertyInput/ColorGradientPropertyInputView.axaml" value="Artemis.UI.Windows/Artemis.UI.Windows.csproj" /> <entry key="Artemis.UI/DefaultTypes/PropertyInput/ColorGradientPropertyInputView.axaml" value="Artemis.UI.Windows/Artemis.UI.Windows.csproj" />
<entry key="Artemis.UI/DefaultTypes/PropertyInput/SKSizePropertyInputView.axaml" value="Artemis.UI.Linux/Artemis.UI.Linux.csproj" /> <entry key="Artemis.UI/DefaultTypes/PropertyInput/SKSizePropertyInputView.axaml" value="Artemis.UI.Linux/Artemis.UI.Linux.csproj" />
<entry key="Artemis.UI/MainWindow.axaml" value="Artemis.UI.Linux/Artemis.UI.Linux.csproj" />
<entry key="Artemis.UI/Screens/Plugins/PluginFeatureView.axaml" value="Artemis.UI.Linux/Artemis.UI.Linux.csproj" /> <entry key="Artemis.UI/Screens/Plugins/PluginFeatureView.axaml" value="Artemis.UI.Linux/Artemis.UI.Linux.csproj" />
<entry key="Artemis.UI/Screens/Plugins/PluginSettingsView.axaml" value="Artemis.UI.Linux/Artemis.UI.Linux.csproj" /> <entry key="Artemis.UI/Screens/Plugins/PluginSettingsView.axaml" value="Artemis.UI.Linux/Artemis.UI.Linux.csproj" />
<entry key="Artemis.UI/Screens/ProfileEditor/Panels/MenuBar/MenuBarView.axaml" value="Artemis.UI.Linux/Artemis.UI.Linux.csproj" />
<entry key="Artemis.UI/Screens/ProfileEditor/Panels/ProfileTree/ProfileTreeView.axaml" value="Artemis.UI.Linux/Artemis.UI.Linux.csproj" />
<entry key="Artemis.UI/Screens/ProfileEditor/Panels/Properties/Dialogs/AddEffectView.axaml" value="Artemis.UI.Windows/Artemis.UI.Windows.csproj" /> <entry key="Artemis.UI/Screens/ProfileEditor/Panels/Properties/Dialogs/AddEffectView.axaml" value="Artemis.UI.Windows/Artemis.UI.Windows.csproj" />
<entry key="Artemis.UI/Screens/ProfileEditor/Panels/Properties/PropertiesView.axaml" value="Artemis.UI.Linux/Artemis.UI.Linux.csproj" /> <entry key="Artemis.UI/Screens/ProfileEditor/Panels/Properties/PropertiesView.axaml" value="Artemis.UI.Linux/Artemis.UI.Linux.csproj" />
<entry key="Artemis.UI/Screens/ProfileEditor/Panels/Properties/Tree/ContentDialogs/LayerEffectRenameView.axaml" value="Artemis.UI.Linux/Artemis.UI.Linux.csproj" /> <entry key="Artemis.UI/Screens/ProfileEditor/Panels/Properties/Tree/ContentDialogs/LayerEffectRenameView.axaml" value="Artemis.UI.Linux/Artemis.UI.Linux.csproj" />
@ -28,9 +31,13 @@
<entry key="Artemis.UI/Screens/ProfileEditor/Panels/Properties/Tree/TreePropertyView.axaml" value="Artemis.UI.Linux/Artemis.UI.Linux.csproj" /> <entry key="Artemis.UI/Screens/ProfileEditor/Panels/Properties/Tree/TreePropertyView.axaml" value="Artemis.UI.Linux/Artemis.UI.Linux.csproj" />
<entry key="Artemis.UI/Screens/ProfileEditor/Panels/Properties/Windows/EffectConfigurationWindowView.axaml" value="Artemis.UI.Linux/Artemis.UI.Linux.csproj" /> <entry key="Artemis.UI/Screens/ProfileEditor/Panels/Properties/Windows/EffectConfigurationWindowView.axaml" value="Artemis.UI.Linux/Artemis.UI.Linux.csproj" />
<entry key="Artemis.UI/Screens/ProfileEditor/Panels/VisualEditor/Tools/TransformToolView.axaml" value="Artemis.UI.Linux/Artemis.UI.Linux.csproj" /> <entry key="Artemis.UI/Screens/ProfileEditor/Panels/VisualEditor/Tools/TransformToolView.axaml" value="Artemis.UI.Linux/Artemis.UI.Linux.csproj" />
<entry key="Artemis.UI/Screens/ProfileEditor/ProfileEditorView.axaml" value="Artemis.UI.Linux/Artemis.UI.Linux.csproj" />
<entry key="Artemis.UI/Screens/Root/SplashView.axaml" value="Artemis.UI.Linux/Artemis.UI.Linux.csproj" /> <entry key="Artemis.UI/Screens/Root/SplashView.axaml" value="Artemis.UI.Linux/Artemis.UI.Linux.csproj" />
<entry key="Artemis.UI/Screens/Settings/Tabs/GeneralTabView.axaml" value="Artemis.UI.Linux/Artemis.UI.Linux.csproj" /> <entry key="Artemis.UI/Screens/Settings/Tabs/GeneralTabView.axaml" value="Artemis.UI.Linux/Artemis.UI.Linux.csproj" />
<entry key="Artemis.UI/Screens/Sidebar/ContentDialogs/SidebarCategoryEditView.axaml" value="Artemis.UI.Linux/Artemis.UI.Linux.csproj" /> <entry key="Artemis.UI/Screens/Sidebar/ContentDialogs/SidebarCategoryEditView.axaml" value="Artemis.UI.Linux/Artemis.UI.Linux.csproj" />
<entry key="Artemis.UI/Screens/Sidebar/SidebarCategoryView.axaml" value="Artemis.UI.Linux/Artemis.UI.Linux.csproj" />
<entry key="Artemis.UI/Screens/Sidebar/SidebarProfileConfigurationView.axaml" value="Artemis.UI.Linux/Artemis.UI.Linux.csproj" />
<entry key="Artemis.UI/Screens/Sidebar/SidebarView.axaml" value="Artemis.UI.Linux/Artemis.UI.Linux.csproj" />
<entry key="Artemis.UI/Screens/VisualScripting/NodeView.axaml" value="Artemis.UI.Linux/Artemis.UI.Linux.csproj" /> <entry key="Artemis.UI/Screens/VisualScripting/NodeView.axaml" value="Artemis.UI.Linux/Artemis.UI.Linux.csproj" />
<entry key="Avalonia/Artemis.UI/Screens/Debugger/Tabs/Render/RenderDebugView.axaml" value="Avalonia/Artemis.UI.Linux/Artemis.UI.Linux.csproj" /> <entry key="Avalonia/Artemis.UI/Screens/Debugger/Tabs/Render/RenderDebugView.axaml" value="Avalonia/Artemis.UI.Linux/Artemis.UI.Linux.csproj" />
<entry key="Avalonia/Artemis.UI/Styles/Artemis.axaml" value="Avalonia/Artemis.UI.Linux/Artemis.UI.Linux.csproj" /> <entry key="Avalonia/Artemis.UI/Styles/Artemis.axaml" value="Avalonia/Artemis.UI.Linux/Artemis.UI.Linux.csproj" />

View File

@ -141,6 +141,20 @@ public interface IProfileEditorService : IArtemisSharedUIService
/// <returns>The rounded time.</returns> /// <returns>The rounded time.</returns>
TimeSpan RoundTime(TimeSpan time); TimeSpan RoundTime(TimeSpan time);
/// <summary>
/// Creates a new folder as a sibling or child of the given target.
/// </summary>
/// <param name="target">The target, if this is a layer the new layer will become a sibling, otherwise a child.</param>
/// <returns>The resulting folder.</returns>
Folder CreateAndAddFolder(ProfileElement target);
/// <summary>
/// Creates a new layer with the default brush and all current LEDs as a sibling or child of the given target.
/// </summary>
/// <param name="target">The target, if this is a layer the new layer will become a sibling, otherwise a child.</param>
/// <returns>The resulting layer.</returns>
Layer CreateAndAddLayer(ProfileElement target);
/// <summary> /// <summary>
/// Executes the provided command and adds it to the history. /// Executes the provided command and adds it to the history.
/// </summary> /// </summary>

View File

@ -18,6 +18,8 @@ internal class ProfileEditorService : IProfileEditorService
private readonly BehaviorSubject<ILayerProperty?> _layerPropertySubject = new(null); private readonly BehaviorSubject<ILayerProperty?> _layerPropertySubject = new(null);
private readonly ILogger _logger; private readonly ILogger _logger;
private readonly IModuleService _moduleService; private readonly IModuleService _moduleService;
private readonly IRgbService _rgbService;
private readonly ILayerBrushService _layerBrushService;
private readonly BehaviorSubject<int> _pixelsPerSecondSubject = new(120); private readonly BehaviorSubject<int> _pixelsPerSecondSubject = new(120);
private readonly BehaviorSubject<bool> _playingSubject = new(false); private readonly BehaviorSubject<bool> _playingSubject = new(false);
private readonly BehaviorSubject<ProfileConfiguration?> _profileConfigurationSubject = new(null); private readonly BehaviorSubject<ProfileConfiguration?> _profileConfigurationSubject = new(null);
@ -30,11 +32,18 @@ internal class ProfileEditorService : IProfileEditorService
private readonly IWindowService _windowService; private readonly IWindowService _windowService;
private ProfileEditorCommandScope? _profileEditorHistoryScope; private ProfileEditorCommandScope? _profileEditorHistoryScope;
public ProfileEditorService(ILogger logger, IProfileService profileService, IModuleService moduleService, IWindowService windowService) public ProfileEditorService(ILogger logger,
IProfileService profileService,
IModuleService moduleService,
IRgbService rgbService,
ILayerBrushService layerBrushService,
IWindowService windowService)
{ {
_logger = logger; _logger = logger;
_profileService = profileService; _profileService = profileService;
_moduleService = moduleService; _moduleService = moduleService;
_rgbService = rgbService;
_layerBrushService = layerBrushService;
_windowService = windowService; _windowService = windowService;
ProfileConfiguration = _profileConfigurationSubject.AsObservable(); ProfileConfiguration = _profileConfigurationSubject.AsObservable();
@ -293,6 +302,48 @@ internal class ProfileEditorService : IProfileEditorService
return TimeSpan.FromMilliseconds(Math.Round(time.TotalMilliseconds)); return TimeSpan.FromMilliseconds(Math.Round(time.TotalMilliseconds));
} }
/// <inheritdoc />
public Folder CreateAndAddFolder(ProfileElement target)
{
if (target is Layer targetLayer)
{
Folder folder = new(targetLayer.Parent, targetLayer.Parent.GetNewFolderName());
ExecuteCommand(new AddProfileElement(folder, targetLayer.Parent, targetLayer.Order));
return folder;
}
else
{
Folder folder = new(target, target.GetNewFolderName());
ExecuteCommand(new AddProfileElement(folder, target, 0));
return folder;
}
}
/// <inheritdoc />
public Layer CreateAndAddLayer(ProfileElement target)
{
if (target is Layer targetLayer)
{
Layer layer = new(targetLayer.Parent, targetLayer.GetNewLayerName());
_layerBrushService.ApplyDefaultBrush(layer);
layer.AddLeds(_rgbService.EnabledDevices.SelectMany(d => d.Leds));
ExecuteCommand(new AddProfileElement(layer, targetLayer.Parent, targetLayer.Order));
return layer;
}
else
{
Layer layer = new(target, target.GetNewLayerName());
_layerBrushService.ApplyDefaultBrush(layer);
layer.AddLeds(_rgbService.EnabledDevices.SelectMany(d => d.Leds));
ExecuteCommand(new AddProfileElement(layer, target, 0));
return layer;
}
}
public void ChangePixelsPerSecond(int pixelsPerSecond) public void ChangePixelsPerSecond(int pixelsPerSecond)
{ {
_pixelsPerSecondSubject.OnNext(pixelsPerSecond); _pixelsPerSecondSubject.OnNext(pixelsPerSecond);

View File

@ -7,9 +7,15 @@
x:Class="Artemis.UI.MainWindow" x:Class="Artemis.UI.MainWindow"
Icon="/Assets/Images/Logo/application.ico" Icon="/Assets/Images/Logo/application.ico"
Title="Artemis 2.0"> Title="Artemis 2.0">
<Panel> <Panel Name="RootPanel">
<DockPanel> <DockPanel>
<ContentControl Content="{Binding SidebarViewModel}" DockPanel.Dock="Left"></ContentControl> <ContentControl Name="SidebarContentControl" Content="{Binding SidebarViewModel}" DockPanel.Dock="Left" Width="240">
<ContentControl.Transitions>
<Transitions>
<DoubleTransition Property="Width" Duration="0:0:0.2" Easing="CubicEaseOut"></DoubleTransition>
</Transitions>
</ContentControl.Transitions>
</ContentControl>
<Border Background="Transparent" Name="TitleBar" DockPanel.Dock="Top"> <Border Background="Transparent" Name="TitleBar" DockPanel.Dock="Top">
<ContentControl Content="{Binding TitleBarViewModel}" /> <ContentControl Content="{Binding TitleBarViewModel}" />
</Border> </Border>

View File

@ -12,15 +12,28 @@ namespace Artemis.UI
{ {
public class MainWindow : ReactiveCoreWindow<RootViewModel> public class MainWindow : ReactiveCoreWindow<RootViewModel>
{ {
private readonly Panel _rootPanel;
private readonly ContentControl _sidebarContentControl;
public MainWindow() public MainWindow()
{ {
Opened += OnOpened; Opened += OnOpened;
InitializeComponent(); InitializeComponent();
_rootPanel = this.Get<Panel>("RootPanel");
_sidebarContentControl = this.Get<ContentControl>("SidebarContentControl");
_rootPanel.LayoutUpdated += OnLayoutUpdated;
#if DEBUG #if DEBUG
this.AttachDevTools(); this.AttachDevTools();
#endif #endif
} }
// TODO: Replace with a media query once https://github.com/AvaloniaUI/Avalonia/pull/7938 is implemented
private void OnLayoutUpdated(object? sender, EventArgs e)
{
_sidebarContentControl.Width = _rootPanel.Bounds.Width >= 1800 ? 300 : 240;
}
private void OnOpened(object? sender, EventArgs e) private void OnOpened(object? sender, EventArgs e)
{ {
Opened -= OnOpened; Opened -= OnOpened;
@ -32,12 +45,6 @@ namespace Artemis.UI
} }
} }
private void SetupTitlebar()
{
}
private void InitializeComponent() private void InitializeComponent()
{ {
AvaloniaXamlLoader.Load(this); AvaloniaXamlLoader.Load(this);

View File

@ -6,11 +6,11 @@
mc:Ignorable="d" d:DesignWidth="300" d:DesignHeight="450" mc:Ignorable="d" d:DesignWidth="300" d:DesignHeight="450"
x:Class="Artemis.UI.Screens.ProfileEditor.DisplayCondition.ConditionTypes.AlwaysOnConditionView"> x:Class="Artemis.UI.Screens.ProfileEditor.DisplayCondition.ConditionTypes.AlwaysOnConditionView">
<DockPanel VerticalAlignment="Top" Margin="0 5"> <DockPanel VerticalAlignment="Top" Margin="0 5">
<avalonia:MaterialIcon Kind="InfoCircle" Margin="5 0"/> <avalonia:MaterialIcon Kind="InfoCircle" Margin="5 0" />
<TextBlock Foreground="{DynamicResource TextFillColorSecondaryBrush}" <TextBlock Foreground="{DynamicResource TextFillColorSecondaryBrush}"
TextWrapping="Wrap"> TextWrapping="Wrap">
After playing the start segment, the main segment is endlessly repeated. After playing the start segment, the main segment is endlessly repeated.
</TextBlock> </TextBlock>
</DockPanel> </DockPanel>
</UserControl> </UserControl>

View File

@ -8,18 +8,18 @@
x:Class="Artemis.UI.Screens.ProfileEditor.DisplayCondition.ConditionTypes.EventConditionView" x:Class="Artemis.UI.Screens.ProfileEditor.DisplayCondition.ConditionTypes.EventConditionView"
x:DataType="conditionTypes:EventConditionViewModel"> x:DataType="conditionTypes:EventConditionViewModel">
<DockPanel HorizontalAlignment="Stretch"> <DockPanel HorizontalAlignment="Stretch">
<DockPanel.Styles> <DockPanel.Styles>
<Style Selector="DockPanel > TextBlock"> <Style Selector="DockPanel > TextBlock">
<Setter Property="Margin" Value="0 5" /> <Setter Property="Margin" Value="0 5" />
</Style> </Style>
</DockPanel.Styles> </DockPanel.Styles>
<TextBlock DockPanel.Dock="Top">Triggered by event</TextBlock> <TextBlock DockPanel.Dock="Top">Triggered by event</TextBlock>
<dataModelPicker:DataModelPickerButton Placeholder="Select an event" <dataModelPicker:DataModelPickerButton Placeholder="Select an event"
DockPanel.Dock="Top" DockPanel.Dock="Top"
HorizontalAlignment="Stretch" HorizontalAlignment="Stretch"
DataModelPath="{CompiledBinding EventPath}" DataModelPath="{CompiledBinding EventPath}"
ShowFullPath="{CompiledBinding ShowFullPaths.Value}" ShowFullPath="{CompiledBinding ShowFullPaths.Value}"
IsEventPicker="True"/> IsEventPicker="True" />
<TextBlock DockPanel.Dock="Top">When the event fires..</TextBlock> <TextBlock DockPanel.Dock="Top">When the event fires..</TextBlock>
<ComboBox PlaceholderText="Select a play mode" HorizontalAlignment="Stretch" DockPanel.Dock="Top" SelectedIndex="{CompiledBinding SelectedTriggerMode}"> <ComboBox PlaceholderText="Select a play mode" HorizontalAlignment="Stretch" DockPanel.Dock="Top" SelectedIndex="{CompiledBinding SelectedTriggerMode}">
@ -54,11 +54,11 @@
DockPanel.Dock="Top" DockPanel.Dock="Top"
SelectedIndex="{CompiledBinding SelectedToggleOffMode}" SelectedIndex="{CompiledBinding SelectedToggleOffMode}"
IsVisible="{CompiledBinding ShowToggleOffOptions}"> IsVisible="{CompiledBinding ShowToggleOffOptions}">
<ComboBoxItem>Finish the main segment</ComboBoxItem> <ComboBoxItem>Finish the main segment</ComboBoxItem>
<ComboBoxItem>Skip forward to the end segment</ComboBoxItem> <ComboBoxItem>Skip forward to the end segment</ComboBoxItem>
</ComboBox> </ComboBox>
<Button DockPanel.Dock="Bottom" <Button DockPanel.Dock="Bottom"
ToolTip.Tip="Open editor" ToolTip.Tip="Open editor"
Margin="0 15 0 5" Margin="0 15 0 5"
Command="{CompiledBinding OpenEditor}" Command="{CompiledBinding OpenEditor}"

View File

@ -6,7 +6,7 @@
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450" mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="Artemis.UI.Screens.ProfileEditor.DisplayCondition.ConditionTypes.PlayOnceConditionView"> x:Class="Artemis.UI.Screens.ProfileEditor.DisplayCondition.ConditionTypes.PlayOnceConditionView">
<DockPanel VerticalAlignment="Top" Margin="0 5"> <DockPanel VerticalAlignment="Top" Margin="0 5">
<avalonia:MaterialIcon Kind="InfoCircle" Margin="5 0"/> <avalonia:MaterialIcon Kind="InfoCircle" Margin="5 0" />
<TextBlock Foreground="{DynamicResource TextFillColorSecondaryBrush}" <TextBlock Foreground="{DynamicResource TextFillColorSecondaryBrush}"
TextWrapping="Wrap"> TextWrapping="Wrap">
Every segment of the timeline is played once. Every segment of the timeline is played once.

View File

@ -7,212 +7,193 @@
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450" mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="Artemis.UI.Screens.ProfileEditor.MenuBar.MenuBarView" x:Class="Artemis.UI.Screens.ProfileEditor.MenuBar.MenuBarView"
x:DataType="menuBar:MenuBarViewModel"> x:DataType="menuBar:MenuBarViewModel">
<Menu VerticalAlignment="Top"> <Menu VerticalAlignment="Top">
<MenuItem Header="_File"> <MenuItem Header="_File">
<MenuItem Header="New"> <MenuItem Header="New">
<MenuItem.Icon> <MenuItem.Icon>
<avalonia:MaterialIcon Kind="Plus" /> <avalonia:MaterialIcon Kind="Plus" />
</MenuItem.Icon> </MenuItem.Icon>
<MenuItem Header="Folder" Command="{Binding AddFolder}"> <MenuItem Header="Folder" Command="{CompiledBinding AddFolder}">
<MenuItem.Icon> <MenuItem.Icon>
<avalonia:MaterialIcon Kind="Folder" /> <avalonia:MaterialIcon Kind="Folder" />
</MenuItem.Icon> </MenuItem.Icon>
</MenuItem> </MenuItem>
<MenuItem Header="Layer" Command="{Binding AddLayer}"> <MenuItem Header="Layer" Command="{CompiledBinding AddLayer}">
<MenuItem.Icon> <MenuItem.Icon>
<avalonia:MaterialIcon Kind="Layers" /> <avalonia:MaterialIcon Kind="Layers" />
</MenuItem.Icon> </MenuItem.Icon>
</MenuItem> </MenuItem>
</MenuItem> </MenuItem>
<Separator /> <Separator />
<MenuItem Header="View Properties" Command="{Binding ViewProperties}"> <MenuItem Header="View Properties" Command="{CompiledBinding ViewProperties}">
<MenuItem.Icon> <MenuItem.Icon>
<avalonia:MaterialIcon Kind="Settings" /> <avalonia:MaterialIcon Kind="Settings" />
</MenuItem.Icon> </MenuItem.Icon>
</MenuItem> </MenuItem>
<MenuItem Header="_View Scripts" Command="{Binding ViewScripts}"> <MenuItem Header="_View Scripts" Command="{Binding ViewScripts}">
<MenuItem.Icon> <MenuItem.Icon>
<avalonia:MaterialIcon Kind="BookEdit" /> <avalonia:MaterialIcon Kind="BookEdit" />
</MenuItem.Icon> </MenuItem.Icon>
</MenuItem> </MenuItem>
<MenuItem Header="Adapt Profile" Command="{Binding AdaptProfile}"> <MenuItem Header="Adapt Profile" Command="{Binding AdaptProfile}">
<MenuItem.Icon> <MenuItem.Icon>
<avalonia:MaterialIcon Kind="Magic" /> <avalonia:MaterialIcon Kind="Magic" />
</MenuItem.Icon> </MenuItem.Icon>
</MenuItem> </MenuItem>
<MenuItem Header="Suspend Profile" IsSelected="{CompiledBinding IsSuspended}"> <MenuItem Header="Suspend Profile" Command="{CompiledBinding ToggleSuspended}">
<MenuItem.Icon> <MenuItem.Icon>
<CheckBox BorderThickness="0" <avalonia:MaterialIcon Kind="Check" IsVisible="{CompiledBinding IsSuspended}" />
IsHitTestVisible="False" </MenuItem.Icon>
IsChecked="{CompiledBinding IsSuspended}"/> </MenuItem>
</MenuItem.Icon> <Separator />
</MenuItem> <MenuItem Header="Export Profile" Command="{CompiledBinding ExportProfile}">
<Separator /> <MenuItem.Icon>
<MenuItem Header="Export Profile" Command="{Binding ExportProfile}"> <avalonia:MaterialIcon Kind="Export" />
<MenuItem.Icon> </MenuItem.Icon>
<avalonia:MaterialIcon Kind="Export" /> </MenuItem>
</MenuItem.Icon> <MenuItem Header="Duplicate Profile" Command="{CompiledBinding DuplicateProfile}">
</MenuItem> <MenuItem.Icon>
<MenuItem Header="Duplicate Profile" Command="{Binding DuplicateProfile}"> <avalonia:MaterialIcon Kind="ContentDuplicate" />
<MenuItem.Icon> </MenuItem.Icon>
<avalonia:MaterialIcon Kind="ContentDuplicate" /> </MenuItem>
</MenuItem.Icon> <Separator />
</MenuItem> <MenuItem Header="Delete Profile" Command="{CompiledBinding DeleteProfile}">
<Separator /> <MenuItem.Icon>
<MenuItem Header="Delete Profile" Command="{Binding DeleteProfile}"> <avalonia:MaterialIcon Kind="Trash" />
<MenuItem.Icon> </MenuItem.Icon>
<avalonia:MaterialIcon Kind="Trash" /> </MenuItem>
</MenuItem.Icon> </MenuItem>
</MenuItem> <MenuItem Header="_Edit" SubmenuOpened="MenuItem_OnSubmenuOpened">
</MenuItem> <MenuItem Header="_Undo" Command="{CompiledBinding History.Undo}" InputGesture="Ctrl+Z">
<MenuItem Header="_Edit" SubmenuOpened="MenuItem_OnSubmenuOpened">
<MenuItem Header="_Undo"
Command="{Binding History.Undo}"
HotKey="Ctrl+Z"
InputGesture="Ctrl+Z">
<MenuItem.Icon> <MenuItem.Icon>
<avalonia:MaterialIcon Kind="Undo" /> <avalonia:MaterialIcon Kind="Undo" />
</MenuItem.Icon> </MenuItem.Icon>
</MenuItem> </MenuItem>
<MenuItem Header="_Redo" <MenuItem Header="_Redo" Command="{CompiledBinding History.Redo}" InputGesture="Ctrl+Y">
Command="{Binding History.Redo}"
HotKey="Ctrl+Y"
InputGesture="Ctrl+Y">
<MenuItem.Icon> <MenuItem.Icon>
<avalonia:MaterialIcon Kind="Redo" /> <avalonia:MaterialIcon Kind="Redo" />
</MenuItem.Icon> </MenuItem.Icon>
</MenuItem> </MenuItem>
<Separator /> <Separator />
<MenuItem Header="_Duplicate" <MenuItem Header="_Duplicate" Command="{Binding Duplicate}" InputGesture="Ctrl+D">
Command="{Binding Duplicate}" <MenuItem.Icon>
HotKey="Ctrl+D" <avalonia:MaterialIcon Kind="ContentDuplicate" />
IsEnabled="{Binding HasSelectedElement}"> </MenuItem.Icon>
<MenuItem.Icon> </MenuItem>
<avalonia:MaterialIcon Kind="ContentDuplicate" /> <MenuItem Header="_Copy" Command="{Binding Copy}" InputGesture="Ctrl+C">
</MenuItem.Icon> <MenuItem.Icon>
</MenuItem> <avalonia:MaterialIcon Kind="ContentCopy" />
<MenuItem Header="_Copy" </MenuItem.Icon>
Command="{Binding Copy}" </MenuItem>
HotKey="Ctrl+C" <MenuItem Header="_Paste" Command="{Binding Paste}" InputGesture="Ctrl+V">
IsEnabled="{Binding HasSelectedElement}"> <MenuItem.Icon>
<MenuItem.Icon> <avalonia:MaterialIconExt Kind="ContentPaste" />
<avalonia:MaterialIcon Kind="ContentCopy" /> </MenuItem.Icon>
</MenuItem.Icon> </MenuItem>
</MenuItem> </MenuItem>
<MenuItem Header="_Paste" <MenuItem Header="_Run">
Command="{Binding Paste}" <MenuItem Header="_Switch run mode" Command="{CompiledBinding ToggleSuspendedEditing}" InputGesture="F5">
HotKey="Ctrl+V"> <MenuItem.Icon>
<MenuItem.Icon> <avalonia:MaterialIcon Kind="SwapHorizontal" />
<avalonia:MaterialIconExt Kind="ContentPaste" /> </MenuItem.Icon>
</MenuItem.Icon> </MenuItem>
</MenuItem> <MenuItem Header="Switch Run Mode on Focus Loss"
</MenuItem> ToolTip.Tip="If enabled, run mode is set to normal on focus loss"
<MenuItem Header="_Run"> InputGesture="Shift+F5"
<MenuItem Header="_Switch run mode" Command="{CompiledBinding ToggleBooleanSetting}"
Command="{Binding ToggleSuspend}" CommandParameter="{CompiledBinding AutoSuspend}">
HotKey="F5"> <MenuItem.Icon>
<MenuItem.Icon> <avalonia:MaterialIcon Kind="Check" IsVisible="{CompiledBinding AutoSuspend.Value}" />
<avalonia:MaterialIcon Kind="SwapHorizontal" /> </MenuItem.Icon>
</MenuItem.Icon> </MenuItem>
</MenuItem> </MenuItem>
<MenuItem Header="Run Profile on Focus Loss" <MenuItem Header="_Options">
ToolTip.Tip="If enabled, run mode is set to normal on focus loss" <MenuItem Header="Focus Selected Layer" ToolTip.Tip="If enabled, displays only the layer you currently have selected"
HotKey="Shift+F5">
<MenuItem.Icon>
<CheckBox BorderThickness="0"
IsHitTestVisible="False"
IsChecked="{Binding StopOnFocusLoss.Value}" />
</MenuItem.Icon>
</MenuItem>
</MenuItem>
<MenuItem Header="_Options">
<MenuItem Header="Focus Selected Layer"
ToolTip.Tip="If enabled, displays only the layer you currently have selected"
Command="{CompiledBinding ToggleBooleanSetting}" Command="{CompiledBinding ToggleBooleanSetting}"
CommandParameter="{CompiledBinding FocusSelectedLayer}"> CommandParameter="{CompiledBinding FocusSelectedLayer}">
<MenuItem.Icon> <MenuItem.Icon>
<avalonia:MaterialIcon Kind="Check" IsVisible="{CompiledBinding FocusSelectedLayer.Value}"></avalonia:MaterialIcon> <avalonia:MaterialIcon Kind="Check" IsVisible="{CompiledBinding FocusSelectedLayer.Value}" />
</MenuItem.Icon> </MenuItem.Icon>
</MenuItem> </MenuItem>
<MenuItem Header="Display Data Model Values" <MenuItem Header="Display Data Model Values"
Command="{CompiledBinding ToggleBooleanSetting}" Command="{CompiledBinding ToggleBooleanSetting}"
CommandParameter="{CompiledBinding ShowDataModelValues}"> CommandParameter="{CompiledBinding ShowDataModelValues}">
<MenuItem.Icon> <MenuItem.Icon>
<avalonia:MaterialIcon Kind="Check" IsVisible="{CompiledBinding ShowDataModelValues.Value}"></avalonia:MaterialIcon> <avalonia:MaterialIcon Kind="Check" IsVisible="{CompiledBinding ShowDataModelValues.Value}" />
</MenuItem.Icon> </MenuItem.Icon>
</MenuItem> </MenuItem>
<MenuItem Header="Display Full Condition Paths" <MenuItem Header="Display Full Condition Paths"
Command="{CompiledBinding ToggleBooleanSetting}" Command="{CompiledBinding ToggleBooleanSetting}"
CommandParameter="{CompiledBinding ShowFullPaths}"> CommandParameter="{CompiledBinding ShowFullPaths}">
<MenuItem.Icon> <MenuItem.Icon>
<avalonia:MaterialIcon Kind="Check" IsVisible="{CompiledBinding ShowFullPaths.Value}"></avalonia:MaterialIcon> <avalonia:MaterialIcon Kind="Check" IsVisible="{CompiledBinding ShowFullPaths.Value}" />
</MenuItem.Icon> </MenuItem.Icon>
</MenuItem> </MenuItem>
<MenuItem Header="Always Display Cable Values" <MenuItem Header="Always Display Cable Values"
ToolTip.Tip="If enabled, cable values are always shown instead of only on hover" ToolTip.Tip="If enabled, cable values are always shown instead of only on hover"
Command="{CompiledBinding ToggleBooleanSetting}" Command="{CompiledBinding ToggleBooleanSetting}"
CommandParameter="{CompiledBinding AlwaysShowValues}"> CommandParameter="{CompiledBinding AlwaysShowValues}">
<MenuItem.Icon> <MenuItem.Icon>
<avalonia:MaterialIcon Kind="Check" IsVisible="{CompiledBinding AlwaysShowValues.Value}"></avalonia:MaterialIcon> <avalonia:MaterialIcon Kind="Check" IsVisible="{CompiledBinding AlwaysShowValues.Value}" />
</MenuItem.Icon> </MenuItem.Icon>
</MenuItem> </MenuItem>
<MenuItem Header="Apply All Data Bindings During Edit" <MenuItem Header="Apply All Data Bindings During Edit"
ToolTip.Tip="If enabled, updates all data bindings instead of only the one you are editing" ToolTip.Tip="If enabled, updates all data bindings instead of only the one you are editing"
Command="{CompiledBinding ToggleBooleanSetting}" Command="{CompiledBinding ToggleBooleanSetting}"
CommandParameter="{CompiledBinding AlwaysApplyDataBindings}"> CommandParameter="{CompiledBinding AlwaysApplyDataBindings}">
<MenuItem.Icon> <MenuItem.Icon>
<avalonia:MaterialIcon Kind="Check" IsVisible="{CompiledBinding AlwaysApplyDataBindings.Value}"></avalonia:MaterialIcon> <avalonia:MaterialIcon Kind="Check" IsVisible="{CompiledBinding AlwaysApplyDataBindings.Value}" />
</MenuItem.Icon> </MenuItem.Icon>
</MenuItem> </MenuItem>
</MenuItem> </MenuItem>
<MenuItem Header="_Help"> <MenuItem Header="_Help">
<MenuItem Header="Artemis Wiki" Command="{Binding OpenUrl}" CommandParameter="https://wiki.artemis-rgb.com/"> <MenuItem Header="Artemis Wiki" Command="{CompiledBinding OpenUri}" CommandParameter="https://wiki.artemis-rgb.com/">
<MenuItem.Icon> <MenuItem.Icon>
<avalonia:MaterialIcon Kind="BookEdit" /> <avalonia:MaterialIcon Kind="BookEdit" />
</MenuItem.Icon> </MenuItem.Icon>
</MenuItem> </MenuItem>
<Separator /> <Separator />
<MenuItem Header="Editor" Command="{Binding OpenUrl}" CommandParameter="https://wiki.artemis-rgb.com/en/guides/user/profiles"> <MenuItem Header="Editor" Command="{CompiledBinding OpenUri}" CommandParameter="https://wiki.artemis-rgb.com/en/guides/user/profiles">
<MenuItem.Icon> <MenuItem.Icon>
<avalonia:MaterialIcon Kind="Edit" /> <avalonia:MaterialIcon Kind="Edit" />
</MenuItem.Icon> </MenuItem.Icon>
</MenuItem> </MenuItem>
<MenuItem Header="Layers" Command="{Binding OpenUrl}" CommandParameter="https://wiki.artemis-rgb.com/guides/user/profiles/layers"> <MenuItem Header="Layers" Command="{CompiledBinding OpenUri}" CommandParameter="https://wiki.artemis-rgb.com/guides/user/profiles/layers">
<MenuItem.Icon> <MenuItem.Icon>
<avalonia:MaterialIcon Kind="Layers" /> <avalonia:MaterialIcon Kind="Layers" />
</MenuItem.Icon> </MenuItem.Icon>
</MenuItem> </MenuItem>
<MenuItem Header="Display Conditions" Command="{Binding OpenUrl}" CommandParameter="https://wiki.artemis-rgb.com/guides/user/profiles/conditions"> <MenuItem Header="Display Conditions" Command="{CompiledBinding OpenUri}" CommandParameter="https://wiki.artemis-rgb.com/guides/user/profiles/conditions">
<MenuItem.Icon> <MenuItem.Icon>
<avalonia:MaterialIcon Kind="NotEqual" /> <avalonia:MaterialIcon Kind="NotEqual" />
</MenuItem.Icon> </MenuItem.Icon>
</MenuItem> </MenuItem>
<MenuItem Header="Timeline" Command="{Binding OpenUrl}" CommandParameter="https://wiki.artemis-rgb.com/guides/user/profiles/timeline"> <MenuItem Header="Timeline" Command="{CompiledBinding OpenUri}" CommandParameter="https://wiki.artemis-rgb.com/guides/user/profiles/timeline">
<MenuItem.Icon> <MenuItem.Icon>
<avalonia:MaterialIcon Kind="Stopwatch" /> <avalonia:MaterialIcon Kind="Stopwatch" />
</MenuItem.Icon> </MenuItem.Icon>
</MenuItem> </MenuItem>
<MenuItem Header="Data Bindings" Command="{Binding OpenUrl}" CommandParameter="https://wiki.artemis-rgb.com/guides/user/profiles/data-bindings"> <MenuItem Header="Data Bindings" Command="{CompiledBinding OpenUri}" CommandParameter="https://wiki.artemis-rgb.com/guides/user/profiles/data-bindings">
<MenuItem.Icon> <MenuItem.Icon>
<avalonia:MaterialIcon Kind="VectorLink" /> <avalonia:MaterialIcon Kind="VectorLink" />
</MenuItem.Icon> </MenuItem.Icon>
</MenuItem> </MenuItem>
<MenuItem Header="Scripting" Command="{Binding OpenUrl}" CommandParameter="https://wiki.artemis-rgb.com/guides/user/profiles/scripting"> <MenuItem Header="Scripting" Command="{CompiledBinding OpenUri}" CommandParameter="https://wiki.artemis-rgb.com/guides/user/profiles/scripting">
<MenuItem.Icon> <MenuItem.Icon>
<avalonia:MaterialIcon Kind="CodeJson" /> <avalonia:MaterialIcon Kind="CodeJson" />
</MenuItem.Icon> </MenuItem.Icon>
</MenuItem> </MenuItem>
<Separator /> <Separator />
<MenuItem Header="Report a Bug" Command="{Binding OpenUrl}" CommandParameter="https://github.com/Artemis-RGB/Artemis/issues"> <MenuItem Header="Report a Bug" Command="{CompiledBinding OpenUri}" CommandParameter="https://github.com/Artemis-RGB/Artemis/issues">
<MenuItem.Icon> <MenuItem.Icon>
<avalonia:MaterialIcon Kind="Github" /> <avalonia:MaterialIcon Kind="Github" />
</MenuItem.Icon> </MenuItem.Icon>
</MenuItem> </MenuItem>
<MenuItem Header="Get Help on Discord" Command="{Binding OpenUrl}" CommandParameter="https://discord.gg/S3MVaC9"> <MenuItem Header="Get Help on Discord" Command="{CompiledBinding OpenUri}" CommandParameter="https://discord.gg/S3MVaC9">
<MenuItem.Icon> <MenuItem.Icon>
<avalonia:MaterialIcon Kind="Discord" /> <avalonia:MaterialIcon Kind="Discord" />
</MenuItem.Icon> </MenuItem.Icon>
</MenuItem> </MenuItem>
</MenuItem> </MenuItem>
</Menu> </Menu>
</UserControl> </UserControl>

View File

@ -2,23 +2,21 @@ using Avalonia.Interactivity;
using Avalonia.Markup.Xaml; using Avalonia.Markup.Xaml;
using Avalonia.ReactiveUI; using Avalonia.ReactiveUI;
namespace Artemis.UI.Screens.ProfileEditor.MenuBar namespace Artemis.UI.Screens.ProfileEditor.MenuBar;
public class MenuBarView : ReactiveUserControl<MenuBarViewModel>
{ {
public partial class MenuBarView : ReactiveUserControl<MenuBarViewModel> public MenuBarView()
{ {
public MenuBarView() InitializeComponent();
{ }
InitializeComponent();
}
private void InitializeComponent() private void InitializeComponent()
{ {
AvaloniaXamlLoader.Load(this); AvaloniaXamlLoader.Load(this);
} }
private void MenuItem_OnSubmenuOpened(object? sender, RoutedEventArgs e) private void MenuItem_OnSubmenuOpened(object? sender, RoutedEventArgs e)
{ {
}
} }
} }

View File

@ -1,31 +1,50 @@
using System; using System;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reactive; using System.Reactive;
using System.Reactive.Disposables; using System.Reactive.Disposables;
using System.Reactive.Linq; using System.Reactive.Linq;
using System.Threading.Tasks;
using Artemis.Core; using Artemis.Core;
using Artemis.Core.Services; using Artemis.Core.Services;
using Artemis.UI.Screens.Sidebar;
using Artemis.UI.Shared; using Artemis.UI.Shared;
using Artemis.UI.Shared.Services;
using Artemis.UI.Shared.Services.ProfileEditor; using Artemis.UI.Shared.Services.ProfileEditor;
using Artemis.UI.Shared.Services.ProfileEditor.Commands;
using Newtonsoft.Json;
using ReactiveUI; using ReactiveUI;
using Serilog;
namespace Artemis.UI.Screens.ProfileEditor.MenuBar; namespace Artemis.UI.Screens.ProfileEditor.MenuBar;
public class MenuBarViewModel : ActivatableViewModelBase public class MenuBarViewModel : ActivatableViewModelBase
{ {
private readonly ILogger _logger;
private readonly IProfileEditorService _profileEditorService;
private readonly IProfileService _profileService; private readonly IProfileService _profileService;
private readonly ISettingsService _settingsService; private readonly ISettingsService _settingsService;
private readonly IWindowService _windowService;
private ProfileEditorHistory? _history; private ProfileEditorHistory? _history;
private ObservableAsPropertyHelper<ProfileConfiguration?>? _profileConfiguration; private ObservableAsPropertyHelper<bool>? _suspendedEditing;
private ObservableAsPropertyHelper<bool>? _isSuspended; private ObservableAsPropertyHelper<bool>? _isSuspended;
private ObservableAsPropertyHelper<ProfileConfiguration?>? _profileConfiguration;
private ObservableAsPropertyHelper<RenderProfileElement?>? _profileElement;
public MenuBarViewModel(IProfileEditorService profileEditorService, IProfileService profileService, ISettingsService settingsService) public MenuBarViewModel(ILogger logger, IProfileEditorService profileEditorService, IProfileService profileService, ISettingsService settingsService, IWindowService windowService)
{ {
_logger = logger;
_profileEditorService = profileEditorService;
_profileService = profileService; _profileService = profileService;
_settingsService = settingsService; _settingsService = settingsService;
_windowService = windowService;
this.WhenActivated(d => this.WhenActivated(d =>
{ {
profileEditorService.History.Subscribe(history => History = history).DisposeWith(d); profileEditorService.History.Subscribe(history => History = history).DisposeWith(d);
_profileConfiguration = profileEditorService.ProfileConfiguration.ToProperty(this, vm => vm.ProfileConfiguration).DisposeWith(d); _profileConfiguration = profileEditorService.ProfileConfiguration.ToProperty(this, vm => vm.ProfileConfiguration).DisposeWith(d);
_profileElement = profileEditorService.ProfileElement.ToProperty(this, vm => vm.ProfileElement).DisposeWith(d);
_suspendedEditing = profileEditorService.SuspendedEditing.ToProperty(this, vm => vm.SuspendedEditing).DisposeWith(d);
_isSuspended = profileEditorService.ProfileConfiguration _isSuspended = profileEditorService.ProfileConfiguration
.Select(p => p?.WhenAnyValue(c => c.IsSuspended) ?? Observable.Never<bool>()) .Select(p => p?.WhenAnyValue(c => c.IsSuspended) ?? Observable.Never<bool>())
.Switch() .Switch()
@ -33,17 +52,35 @@ public class MenuBarViewModel : ActivatableViewModelBase
.DisposeWith(d); .DisposeWith(d);
}); });
AddFolder = ReactiveCommand.Create(ExecuteAddFolder);
AddLayer = ReactiveCommand.Create(ExecuteAddLayer);
ViewProperties = ReactiveCommand.CreateFromTask(ExecuteViewProperties, this.WhenAnyValue(vm => vm.ProfileConfiguration).Select(c => c != null));
ToggleSuspended = ReactiveCommand.Create(ExecuteToggleSuspended, this.WhenAnyValue(vm => vm.ProfileConfiguration).Select(c => c != null));
DeleteProfile = ReactiveCommand.CreateFromTask(ExecuteDeleteProfile, this.WhenAnyValue(vm => vm.ProfileConfiguration).Select(c => c != null));
ExportProfile = ReactiveCommand.CreateFromTask(ExecuteExportProfile, this.WhenAnyValue(vm => vm.ProfileConfiguration).Select(c => c != null));
DuplicateProfile = ReactiveCommand.Create(ExecuteDuplicateProfile, this.WhenAnyValue(vm => vm.ProfileConfiguration).Select(c => c != null));
ToggleSuspendedEditing = ReactiveCommand.Create(ExecuteToggleSuspendedEditing);
ToggleBooleanSetting = ReactiveCommand.Create<PluginSetting<bool>>(ExecuteToggleBooleanSetting); ToggleBooleanSetting = ReactiveCommand.Create<PluginSetting<bool>>(ExecuteToggleBooleanSetting);
OpenUri = ReactiveCommand.CreateFromTask<string>(ExecuteOpenUri);
} }
private void ExecuteToggleBooleanSetting(PluginSetting<bool> setting) public ReactiveCommand<Unit, Unit> AddFolder { get; }
{ public ReactiveCommand<Unit, Unit> AddLayer { get; }
setting.Value = !setting.Value; public ReactiveCommand<Unit, Unit> ToggleSuspended { get; }
setting.Save(); public ReactiveCommand<Unit, Unit> ViewProperties { get; }
} public ReactiveCommand<Unit, Unit> DeleteProfile { get; }
public ReactiveCommand<Unit, Unit> ExportProfile { get; }
public ReactiveCommand<Unit, Unit> DuplicateProfile { get; }
public ReactiveCommand<PluginSetting<bool>, Unit> ToggleBooleanSetting { get; } public ReactiveCommand<PluginSetting<bool>, Unit> ToggleBooleanSetting { get; }
public ReactiveCommand<string, Unit> OpenUri { get; }
public ReactiveCommand<Unit, Unit> ToggleSuspendedEditing { get; }
public ProfileConfiguration? ProfileConfiguration => _profileConfiguration?.Value; public ProfileConfiguration? ProfileConfiguration => _profileConfiguration?.Value;
public RenderProfileElement? ProfileElement => _profileElement?.Value;
public bool IsSuspended => _isSuspended?.Value ?? false;
public bool SuspendedEditing => _suspendedEditing?.Value ?? false;
public PluginSetting<bool> AutoSuspend => _settingsService.GetSetting("ProfileEditor.AutoSuspend", true);
public PluginSetting<bool> FocusSelectedLayer => _settingsService.GetSetting("ProfileEditor.FocusSelectedLayer", false); public PluginSetting<bool> FocusSelectedLayer => _settingsService.GetSetting("ProfileEditor.FocusSelectedLayer", false);
public PluginSetting<bool> ShowDataModelValues => _settingsService.GetSetting("ProfileEditor.ShowDataModelValues", false); public PluginSetting<bool> ShowDataModelValues => _settingsService.GetSetting("ProfileEditor.ShowDataModelValues", false);
public PluginSetting<bool> ShowFullPaths => _settingsService.GetSetting("ProfileEditor.ShowFullPaths", false); public PluginSetting<bool> ShowFullPaths => _settingsService.GetSetting("ProfileEditor.ShowFullPaths", false);
@ -56,16 +93,113 @@ public class MenuBarViewModel : ActivatableViewModelBase
set => RaiseAndSetIfChanged(ref _history, value); set => RaiseAndSetIfChanged(ref _history, value);
} }
public bool IsSuspended private void ExecuteAddFolder()
{ {
get => _isSuspended?.Value ?? false; if (ProfileConfiguration?.Profile == null)
set return;
{
if (ProfileConfiguration == null)
return;
ProfileConfiguration.IsSuspended = value; RenderProfileElement target = ProfileElement ?? ProfileConfiguration.Profile.GetRootFolder();
_profileService.SaveProfileCategory(ProfileConfiguration.Category); _profileEditorService.CreateAndAddFolder(target);
}
private void ExecuteAddLayer()
{
if (ProfileConfiguration?.Profile == null)
return;
RenderProfileElement target = ProfileElement ?? ProfileConfiguration.Profile.GetRootFolder();
_profileEditorService.CreateAndAddLayer(target);
}
private async Task ExecuteViewProperties()
{
if (ProfileConfiguration == null)
return;
await _windowService.ShowDialogAsync<ProfileConfigurationEditViewModel, ProfileConfiguration?>(
("profileCategory", ProfileConfiguration.Category),
("profileConfiguration", ProfileConfiguration)
);
}
private void ExecuteToggleSuspended()
{
if (ProfileConfiguration == null)
return;
ProfileConfiguration.IsSuspended = !ProfileConfiguration.IsSuspended;
_profileService.SaveProfileCategory(ProfileConfiguration.Category);
}
private async Task ExecuteDeleteProfile()
{
if (ProfileConfiguration == null)
return;
if (!await _windowService.ShowConfirmContentDialog("Delete profile", "Are you sure you want to permanently delete this profile?"))
return;
if (ProfileConfiguration.IsBeingEdited)
_profileEditorService.ChangeCurrentProfileConfiguration(null);
_profileService.RemoveProfileConfiguration(ProfileConfiguration);
}
private async Task ExecuteExportProfile()
{
if (ProfileConfiguration == null)
return;
// Might not cover everything but then the dialog will complain and that's good enough
string fileName = Path.GetInvalidFileNameChars().Aggregate(ProfileConfiguration.Name, (current, c) => current.Replace(c, '-'));
string? result = await _windowService.CreateSaveFileDialog()
.HavingFilter(f => f.WithExtension("json").WithName("Artemis profile"))
.WithInitialFileName(fileName)
.ShowAsync();
if (result == null)
return;
ProfileConfigurationExportModel export = _profileService.ExportProfile(ProfileConfiguration);
string json = JsonConvert.SerializeObject(export, IProfileService.ExportSettings);
try
{
await File.WriteAllTextAsync(result, json);
}
catch (Exception e)
{
_windowService.ShowExceptionDialog("Failed to export profile", e);
}
}
private void ExecuteDuplicateProfile()
{
if (ProfileConfiguration == null)
return;
ProfileConfigurationExportModel export = _profileService.ExportProfile(ProfileConfiguration);
_profileService.ImportProfile(ProfileConfiguration.Category, export, true, false, "copy");
}
private void ExecuteToggleSuspendedEditing()
{
_profileEditorService.ChangeSuspendedEditing(!SuspendedEditing);
}
private void ExecuteToggleBooleanSetting(PluginSetting<bool> setting)
{
setting.Value = !setting.Value;
setting.Save();
}
private async Task ExecuteOpenUri(string uri)
{
try
{
Process.Start(new ProcessStartInfo(uri) {UseShellExecute = true, Verb = "open"});
}
catch (Exception e)
{
_logger.Error(e, "Failed to open URL");
await _windowService.ShowConfirmContentDialog("Failed to open URL", "We couldn't open the URL for you, check the logs for more details", "Confirm", null);
} }
} }
} }

View File

@ -60,6 +60,6 @@
</ToggleButton> </ToggleButton>
</StackPanel> </StackPanel>
<TextBlock Classes="h4" Text="{CompiledBinding FormattedCurrentTime, FallbackValue=00.000}" HorizontalAlignment="Right" VerticalAlignment="Center" Margin="0"/> <TextBlock Classes="h4" Text="{CompiledBinding FormattedCurrentTime, FallbackValue=00.000}" HorizontalAlignment="Right" VerticalAlignment="Center" Margin="0" />
</DockPanel> </DockPanel>
</UserControl> </UserControl>

View File

@ -1,18 +1,17 @@
using Avalonia.Markup.Xaml; using Avalonia.Markup.Xaml;
using Avalonia.ReactiveUI; using Avalonia.ReactiveUI;
namespace Artemis.UI.Screens.ProfileEditor.Playback namespace Artemis.UI.Screens.ProfileEditor.Playback;
{
public partial class PlaybackView : ReactiveUserControl<PlaybackViewModel>
{
public PlaybackView()
{
InitializeComponent();
}
private void InitializeComponent() public class PlaybackView : ReactiveUserControl<PlaybackViewModel>
{ {
AvaloniaXamlLoader.Load(this); public PlaybackView()
} {
InitializeComponent();
}
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
} }
} }

View File

@ -14,14 +14,14 @@ public class PlaybackViewModel : ActivatableViewModelBase
{ {
private readonly IProfileEditorService _profileEditorService; private readonly IProfileEditorService _profileEditorService;
private readonly ISettingsService _settingsService; private readonly ISettingsService _settingsService;
private RenderProfileElement? _profileElement;
private ObservableAsPropertyHelper<TimeSpan>? _currentTime; private ObservableAsPropertyHelper<TimeSpan>? _currentTime;
private ObservableAsPropertyHelper<string?>? _formattedCurrentTime; private ObservableAsPropertyHelper<string?>? _formattedCurrentTime;
private ObservableAsPropertyHelper<bool>? _playing;
private bool _repeating;
private bool _repeatTimeline;
private bool _repeatSegment;
private DateTime _lastUpdate; private DateTime _lastUpdate;
private ObservableAsPropertyHelper<bool>? _playing;
private RenderProfileElement? _profileElement;
private bool _repeating;
private bool _repeatSegment;
private bool _repeatTimeline;
public PlaybackViewModel(IProfileEditorService profileEditorService, ISettingsService settingsService) public PlaybackViewModel(IProfileEditorService profileEditorService, ISettingsService settingsService)
{ {

View File

@ -2,7 +2,6 @@
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using Artemis.Core; using Artemis.Core;
using Artemis.Core.Services;
using Artemis.Storage.Entities.Profile; using Artemis.Storage.Entities.Profile;
using Artemis.UI.Extensions; using Artemis.UI.Extensions;
using Artemis.UI.Ninject.Factories; using Artemis.UI.Ninject.Factories;
@ -14,21 +13,14 @@ namespace Artemis.UI.Screens.ProfileEditor.ProfileTree;
public class FolderTreeItemViewModel : TreeItemViewModel public class FolderTreeItemViewModel : TreeItemViewModel
{ {
public FolderTreeItemViewModel(TreeItemViewModel? parent, public FolderTreeItemViewModel(TreeItemViewModel? parent, Folder folder, IWindowService windowService, IProfileEditorService profileEditorService, IProfileEditorVmFactory profileEditorVmFactory)
Folder folder, : base(parent, folder, windowService, profileEditorService, profileEditorVmFactory)
IWindowService windowService,
IProfileEditorService profileEditorService,
ILayerBrushService layerBrushService,
IProfileEditorVmFactory profileEditorVmFactory,
IRgbService rgbService)
: base(parent, folder, windowService, profileEditorService, rgbService, layerBrushService, profileEditorVmFactory)
{ {
Folder = folder; Folder = folder;
} }
public Folder Folder { get; } public Folder Folder { get; }
#region Overrides of TreeItemViewModel #region Overrides of TreeItemViewModel
protected override async Task ExecuteDuplicate() protected override async Task ExecuteDuplicate()

View File

@ -2,7 +2,6 @@
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using Artemis.Core; using Artemis.Core;
using Artemis.Core.Services;
using Artemis.Storage.Entities.Profile; using Artemis.Storage.Entities.Profile;
using Artemis.UI.Extensions; using Artemis.UI.Extensions;
using Artemis.UI.Ninject.Factories; using Artemis.UI.Ninject.Factories;
@ -14,14 +13,8 @@ namespace Artemis.UI.Screens.ProfileEditor.ProfileTree;
public class LayerTreeItemViewModel : TreeItemViewModel public class LayerTreeItemViewModel : TreeItemViewModel
{ {
public LayerTreeItemViewModel(TreeItemViewModel? parent, public LayerTreeItemViewModel(TreeItemViewModel? parent, Layer layer, IWindowService windowService, IProfileEditorService profileEditorService, IProfileEditorVmFactory profileEditorVmFactory)
Layer layer, : base(parent, layer, windowService, profileEditorService, profileEditorVmFactory)
IWindowService windowService,
IProfileEditorService profileEditorService,
IRgbService rgbService,
ILayerBrushService layerBrushService,
IProfileEditorVmFactory profileEditorVmFactory)
: base(parent, layer, windowService, profileEditorService, rgbService, layerBrushService, profileEditorVmFactory)
{ {
Layer = layer; Layer = layer;
} }

View File

@ -5,7 +5,6 @@ using System.Linq;
using System.Reactive.Disposables; using System.Reactive.Disposables;
using System.Threading.Tasks; using System.Threading.Tasks;
using Artemis.Core; using Artemis.Core;
using Artemis.Core.Services;
using Artemis.UI.Ninject.Factories; using Artemis.UI.Ninject.Factories;
using Artemis.UI.Shared.Services; using Artemis.UI.Shared.Services;
using Artemis.UI.Shared.Services.ProfileEditor; using Artemis.UI.Shared.Services.ProfileEditor;
@ -18,12 +17,8 @@ public class ProfileTreeViewModel : TreeItemViewModel
private readonly IProfileEditorService _profileEditorService; private readonly IProfileEditorService _profileEditorService;
private TreeItemViewModel? _selectedChild; private TreeItemViewModel? _selectedChild;
public ProfileTreeViewModel(IWindowService windowService, public ProfileTreeViewModel(IWindowService windowService, IProfileEditorService profileEditorService, IProfileEditorVmFactory profileEditorVmFactory)
IProfileEditorService profileEditorService, : base(null, null, windowService, profileEditorService, profileEditorVmFactory)
ILayerBrushService layerBrushService,
IProfileEditorVmFactory profileEditorVmFactory,
IRgbService rgbService)
: base(null, null, windowService, profileEditorService, rgbService, layerBrushService, profileEditorVmFactory)
{ {
_profileEditorService = profileEditorService; _profileEditorService = profileEditorService;
this.WhenActivated(d => this.WhenActivated(d =>

View File

@ -7,7 +7,6 @@ using System.Reactive.Disposables;
using System.Reactive.Linq; using System.Reactive.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using Artemis.Core; using Artemis.Core;
using Artemis.Core.Services;
using Artemis.UI.Extensions; using Artemis.UI.Extensions;
using Artemis.UI.Ninject.Factories; using Artemis.UI.Ninject.Factories;
using Artemis.UI.Shared; using Artemis.UI.Shared;
@ -21,9 +20,7 @@ namespace Artemis.UI.Screens.ProfileEditor.ProfileTree;
public abstract class TreeItemViewModel : ActivatableViewModelBase public abstract class TreeItemViewModel : ActivatableViewModelBase
{ {
private readonly ILayerBrushService _layerBrushService;
private readonly IProfileEditorVmFactory _profileEditorVmFactory; private readonly IProfileEditorVmFactory _profileEditorVmFactory;
private readonly IRgbService _rgbService;
private readonly IWindowService _windowService; private readonly IWindowService _windowService;
protected readonly IProfileEditorService ProfileEditorService; protected readonly IProfileEditorService ProfileEditorService;
private bool _canPaste; private bool _canPaste;
@ -34,16 +31,13 @@ public abstract class TreeItemViewModel : ActivatableViewModelBase
private string? _renameValue; private string? _renameValue;
private bool _renaming; private bool _renaming;
protected TreeItemViewModel(TreeItemViewModel? parent, ProfileElement? profileElement, protected TreeItemViewModel(TreeItemViewModel? parent,
ProfileElement? profileElement,
IWindowService windowService, IWindowService windowService,
IProfileEditorService profileEditorService, IProfileEditorService profileEditorService,
IRgbService rgbService,
ILayerBrushService layerBrushService,
IProfileEditorVmFactory profileEditorVmFactory) IProfileEditorVmFactory profileEditorVmFactory)
{ {
ProfileEditorService = profileEditorService; ProfileEditorService = profileEditorService;
_rgbService = rgbService;
_layerBrushService = layerBrushService;
_windowService = windowService; _windowService = windowService;
_profileEditorVmFactory = profileEditorVmFactory; _profileEditorVmFactory = profileEditorVmFactory;
@ -126,9 +120,10 @@ public abstract class TreeItemViewModel : ActivatableViewModelBase
foreach (IBreakableModel current in broken) foreach (IBreakableModel current in broken)
{ {
_windowService.ShowExceptionDialog($"{current.BrokenDisplayName} - {current.BrokenState}", current.BrokenStateException!); _windowService.ShowExceptionDialog($"{current.BrokenDisplayName} - {current.BrokenState}", current.BrokenStateException!);
if (broken.Last() != current) if (broken.Last() == current)
if (!await _windowService.ShowConfirmContentDialog("Broken state", "Do you want to view the next exception?")) continue;
return; if (!await _windowService.ShowConfirmContentDialog("Broken state", "Do you want to view the next exception?"))
return;
} }
} }
@ -243,30 +238,14 @@ public abstract class TreeItemViewModel : ActivatableViewModelBase
private void ExecuteAddFolder() private void ExecuteAddFolder()
{ {
if (ProfileElement is Layer targetLayer) if (ProfileElement != null)
ProfileEditorService.ExecuteCommand(new AddProfileElement(new Folder(targetLayer.Parent, targetLayer.Parent.GetNewFolderName()), targetLayer.Parent, targetLayer.Order)); ProfileEditorService.CreateAndAddFolder(ProfileElement);
else if (ProfileElement != null)
ProfileEditorService.ExecuteCommand(new AddProfileElement(new Folder(ProfileElement, ProfileElement.GetNewFolderName()), ProfileElement, 0));
} }
private void ExecuteAddLayer() private void ExecuteAddLayer()
{ {
if (ProfileElement is Layer targetLayer) if (ProfileElement != null)
{ ProfileEditorService.CreateAndAddLayer(ProfileElement);
Layer layer = new(targetLayer.Parent, targetLayer.GetNewLayerName());
_layerBrushService.ApplyDefaultBrush(layer);
layer.AddLeds(_rgbService.EnabledDevices.SelectMany(d => d.Leds));
ProfileEditorService.ExecuteCommand(new AddProfileElement(layer, targetLayer.Parent, targetLayer.Order));
}
else if (ProfileElement != null)
{
Layer layer = new(ProfileElement, ProfileElement.GetNewLayerName());
_layerBrushService.ApplyDefaultBrush(layer);
layer.AddLeds(_rgbService.EnabledDevices.SelectMany(d => d.Leds));
ProfileEditorService.ExecuteCommand(new AddProfileElement(layer, ProfileElement, 0));
}
} }
private async void UpdateCanPaste(bool isFlyoutOpen) private async void UpdateCanPaste(bool isFlyoutOpen)

View File

@ -27,7 +27,7 @@
HorizontalAlignment="Right" HorizontalAlignment="Right"
Margin="10" Margin="10"
Command="{Binding OpenEditor}"> Command="{Binding OpenEditor}">
<avalonia:MaterialIcon Kind="OpenInNew"></avalonia:MaterialIcon> <avalonia:MaterialIcon Kind="OpenInNew" />
</Button> </Button>
</Grid> </Grid>

View File

@ -1,18 +1,17 @@
using Avalonia.Markup.Xaml; using Avalonia.Markup.Xaml;
using Avalonia.ReactiveUI; using Avalonia.ReactiveUI;
namespace Artemis.UI.Screens.ProfileEditor.Properties.DataBinding namespace Artemis.UI.Screens.ProfileEditor.Properties.DataBinding;
{
public partial class DataBindingView : ReactiveUserControl<DataBindingViewModel>
{
public DataBindingView()
{
InitializeComponent();
}
private void InitializeComponent() public class DataBindingView : ReactiveUserControl<DataBindingViewModel>
{ {
AvaloniaXamlLoader.Load(this); public DataBindingView()
} {
InitializeComponent();
}
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
} }
} }

View File

@ -11,7 +11,7 @@
x:DataType="dialogs:AddEffectViewModel" x:DataType="dialogs:AddEffectViewModel"
Width="500"> Width="500">
<Grid RowDefinitions="Auto,*"> <Grid RowDefinitions="Auto,*">
<TextBox Name="SearchBox" Text="{CompiledBinding SearchText}" Margin="0 0 0 15" Watermark="Search"></TextBox> <TextBox Name="SearchBox" Text="{CompiledBinding SearchText}" Margin="0 0 0 15" Watermark="Search" />
<ListBox Name="EffectDescriptorsList" <ListBox Name="EffectDescriptorsList"
Grid.Row="1" Grid.Row="1"
Items="{CompiledBinding LayerEffectDescriptors}" Items="{CompiledBinding LayerEffectDescriptors}"
@ -51,14 +51,14 @@
</DataTemplate> </DataTemplate>
</ListBox.DataTemplates> </ListBox.DataTemplates>
</ListBox> </ListBox>
<Grid Grid.Row="1" Height="300"> <Grid Grid.Row="1" Height="300">
<StackPanel VerticalAlignment="Center" <StackPanel VerticalAlignment="Center"
Spacing="20" Spacing="20"
IsVisible="{CompiledBinding !LayerEffectDescriptors.Count}"> IsVisible="{CompiledBinding !LayerEffectDescriptors.Count}">
<avalonia:MaterialIcon Kind="CloseCircle" Width="32" Height="32"></avalonia:MaterialIcon> <avalonia:MaterialIcon Kind="CloseCircle" Width="32" Height="32" />
<TextBlock Classes="h5" TextAlignment="Center">None of the effects match your search</TextBlock> <TextBlock Classes="h5" TextAlignment="Center">None of the effects match your search</TextBlock>
</StackPanel> </StackPanel>
</Grid> </Grid>
</Grid> </Grid>
</UserControl> </UserControl>

View File

@ -14,8 +14,8 @@ namespace Artemis.UI.Screens.ProfileEditor.Properties.Dialogs;
public class AddEffectViewModel : ContentDialogViewModelBase public class AddEffectViewModel : ContentDialogViewModelBase
{ {
private readonly RenderProfileElement _renderProfileElement;
private readonly IProfileEditorService _profileEditorService; private readonly IProfileEditorService _profileEditorService;
private readonly RenderProfileElement _renderProfileElement;
private string? _searchText; private string? _searchText;
public AddEffectViewModel(RenderProfileElement renderProfileElement, IProfileEditorService profileEditorService, ILayerEffectService layerEffectService) public AddEffectViewModel(RenderProfileElement renderProfileElement, IProfileEditorService profileEditorService, ILayerEffectService layerEffectService)
@ -39,7 +39,7 @@ public class AddEffectViewModel : ContentDialogViewModelBase
public string? SearchText public string? SearchText
{ {
get => _searchText; get => _searchText;
set => this.RaiseAndSetIfChanged(ref _searchText, value); set => RaiseAndSetIfChanged(ref _searchText, value);
} }
public void AddLayerEffect(LayerEffectDescriptor descriptor) public void AddLayerEffect(LayerEffectDescriptor descriptor)
@ -59,4 +59,3 @@ public class AddEffectViewModel : ContentDialogViewModelBase
data.Description.Contains(search, StringComparison.InvariantCultureIgnoreCase); data.Description.Contains(search, StringComparison.InvariantCultureIgnoreCase);
} }
} }

View File

@ -5,7 +5,6 @@
xmlns:controls="clr-namespace:Artemis.UI.Controls" xmlns:controls="clr-namespace:Artemis.UI.Controls"
xmlns:local="clr-namespace:Artemis.UI.Screens.ProfileEditor.Properties" xmlns:local="clr-namespace:Artemis.UI.Screens.ProfileEditor.Properties"
xmlns:converters="clr-namespace:Artemis.UI.Converters" xmlns:converters="clr-namespace:Artemis.UI.Converters"
xmlns:avalonia="clr-namespace:Material.Icons.Avalonia;assembly=Material.Icons.Avalonia"
mc:Ignorable="d" d:DesignWidth="1200" d:DesignHeight="350" mc:Ignorable="d" d:DesignWidth="1200" d:DesignHeight="350"
x:Class="Artemis.UI.Screens.ProfileEditor.Properties.PropertiesView" x:Class="Artemis.UI.Screens.ProfileEditor.Properties.PropertiesView"
x:DataType="local:PropertiesViewModel"> x:DataType="local:PropertiesViewModel">
@ -13,7 +12,7 @@
<StyleInclude Source="/Screens/ProfileEditor/Panels/Properties/Timeline/Segments/Segment.axaml" /> <StyleInclude Source="/Screens/ProfileEditor/Panels/Properties/Timeline/Segments/Segment.axaml" />
</UserControl.Styles> </UserControl.Styles>
<UserControl.Resources> <UserControl.Resources>
<converters:DoubleToGridLengthConverter x:Key="DoubleToGridLengthConverter"></converters:DoubleToGridLengthConverter> <converters:DoubleToGridLengthConverter x:Key="DoubleToGridLengthConverter" />
</UserControl.Resources> </UserControl.Resources>
<Grid Name="ContainerGrid"> <Grid Name="ContainerGrid">
<Grid.ColumnDefinitions> <Grid.ColumnDefinitions>
@ -89,8 +88,7 @@
StartPoint="0,0" StartPoint="0,0"
EndPoint="{CompiledBinding #ContainerGrid.Bounds.BottomLeft}" EndPoint="{CompiledBinding #ContainerGrid.Bounds.BottomLeft}"
StrokeThickness="2" StrokeThickness="2"
Stroke="{DynamicResource SystemAccentColorLight1}"> Stroke="{DynamicResource SystemAccentColorLight1}" />
</Line>
<Line Name="StartSegmentLine" <Line Name="StartSegmentLine"
Canvas.Left="{CompiledBinding TimelineViewModel.StartSegmentViewModel.EndX}" Canvas.Left="{CompiledBinding TimelineViewModel.StartSegmentViewModel.EndX}"
@ -100,8 +98,7 @@
StrokeThickness="2" StrokeThickness="2"
Stroke="{DynamicResource SystemAccentColorLight1}" Stroke="{DynamicResource SystemAccentColorLight1}"
StrokeDashArray="6,2" StrokeDashArray="6,2"
Opacity="0.5"> Opacity="0.5" />
</Line>
<Line Name="MainSegmentLine" <Line Name="MainSegmentLine"
Canvas.Left="{CompiledBinding TimelineViewModel.MainSegmentViewModel.EndX}" Canvas.Left="{CompiledBinding TimelineViewModel.MainSegmentViewModel.EndX}"
IsVisible="{CompiledBinding !TimelineViewModel.MainSegmentViewModel.ShowAddMain}" IsVisible="{CompiledBinding !TimelineViewModel.MainSegmentViewModel.ShowAddMain}"
@ -110,8 +107,7 @@
StrokeThickness="2" StrokeThickness="2"
Stroke="{DynamicResource SystemAccentColorLight1}" Stroke="{DynamicResource SystemAccentColorLight1}"
StrokeDashArray="6,2" StrokeDashArray="6,2"
Opacity="0.5"> Opacity="0.5" />
</Line>
<Line Name="EndSegmentLine" <Line Name="EndSegmentLine"
Canvas.Left="{CompiledBinding TimelineViewModel.EndSegmentViewModel.EndX}" Canvas.Left="{CompiledBinding TimelineViewModel.EndSegmentViewModel.EndX}"
IsVisible="{CompiledBinding !TimelineViewModel.MainSegmentViewModel.ShowAddEnd}" IsVisible="{CompiledBinding !TimelineViewModel.MainSegmentViewModel.ShowAddEnd}"
@ -120,8 +116,7 @@
StrokeThickness="2" StrokeThickness="2"
Stroke="{DynamicResource SystemAccentColorLight1}" Stroke="{DynamicResource SystemAccentColorLight1}"
StrokeDashArray="6,2" StrokeDashArray="6,2"
Opacity="0.5"> Opacity="0.5" />
</Line>
<!-- Timeline segments --> <!-- Timeline segments -->
<ContentControl Canvas.Left="{CompiledBinding TimelineViewModel.EndSegmentViewModel.StartX}" <ContentControl Canvas.Left="{CompiledBinding TimelineViewModel.EndSegmentViewModel.StartX}"
@ -142,8 +137,7 @@
PointerReleased="TimelineCaret_OnPointerReleased" PointerReleased="TimelineCaret_OnPointerReleased"
PointerMoved="TimelineCaret_OnPointerMoved" PointerMoved="TimelineCaret_OnPointerMoved"
Points="-8,0 -8,8 0,20, 8,8 8,0" Points="-8,0 -8,8 0,20, 8,8 8,0"
Fill="{DynamicResource SystemAccentColorLight1}"> Fill="{DynamicResource SystemAccentColorLight1}" />
</Polygon>
</Canvas> </Canvas>
<ScrollViewer Grid.Row="1" <ScrollViewer Grid.Row="1"

View File

@ -15,10 +15,8 @@ using Artemis.UI.Screens.ProfileEditor.Playback;
using Artemis.UI.Screens.ProfileEditor.Properties.DataBinding; using Artemis.UI.Screens.ProfileEditor.Properties.DataBinding;
using Artemis.UI.Screens.ProfileEditor.Properties.Dialogs; using Artemis.UI.Screens.ProfileEditor.Properties.Dialogs;
using Artemis.UI.Screens.ProfileEditor.Properties.Timeline; using Artemis.UI.Screens.ProfileEditor.Properties.Timeline;
using Artemis.UI.Screens.Sidebar;
using Artemis.UI.Shared; using Artemis.UI.Shared;
using Artemis.UI.Shared.Services; using Artemis.UI.Shared.Services;
using Artemis.UI.Shared.Services.Builders;
using Artemis.UI.Shared.Services.ProfileEditor; using Artemis.UI.Shared.Services.ProfileEditor;
using ReactiveUI; using ReactiveUI;
@ -28,11 +26,11 @@ public class PropertiesViewModel : ActivatableViewModelBase
{ {
private readonly Dictionary<LayerPropertyGroup, PropertyGroupViewModel> _cachedPropertyViewModels; private readonly Dictionary<LayerPropertyGroup, PropertyGroupViewModel> _cachedPropertyViewModels;
private readonly IDataBindingVmFactory _dataBindingVmFactory; private readonly IDataBindingVmFactory _dataBindingVmFactory;
private readonly IWindowService _windowService;
private readonly ILayerEffectService _layerEffectService; private readonly ILayerEffectService _layerEffectService;
private readonly ILayerPropertyVmFactory _layerPropertyVmFactory; private readonly ILayerPropertyVmFactory _layerPropertyVmFactory;
private readonly IProfileEditorService _profileEditorService; private readonly IProfileEditorService _profileEditorService;
private readonly ISettingsService _settingsService; private readonly ISettingsService _settingsService;
private readonly IWindowService _windowService;
private DataBindingViewModel? _backgroundDataBindingViewModel; private DataBindingViewModel? _backgroundDataBindingViewModel;
private DataBindingViewModel? _dataBindingViewModel; private DataBindingViewModel? _dataBindingViewModel;
private ObservableAsPropertyHelper<ILayerProperty?>? _layerProperty; private ObservableAsPropertyHelper<ILayerProperty?>? _layerProperty;

View File

@ -1,18 +1,17 @@
using Avalonia.Controls; using Avalonia.Controls;
using Avalonia.Markup.Xaml; using Avalonia.Markup.Xaml;
namespace Artemis.UI.Screens.ProfileEditor.Properties.Timeline.Keyframes namespace Artemis.UI.Screens.ProfileEditor.Properties.Timeline.Keyframes;
{
public partial class TimelineEasingView : UserControl
{
public TimelineEasingView()
{
InitializeComponent();
}
private void InitializeComponent() public class TimelineEasingView : UserControl
{ {
AvaloniaXamlLoader.Load(this); public TimelineEasingView()
} {
InitializeComponent();
}
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
} }
} }

View File

@ -44,8 +44,8 @@
</Template> </Template>
</Setter.Value> </Setter.Value>
</Setter> </Setter>
<Setter Property="Command" Value="{Binding $parent[UserControl].DataContext.SelectEasingFunction}"></Setter> <Setter Property="Command" Value="{Binding $parent[UserControl].DataContext.SelectEasingFunction}" />
<Setter Property="CommandParameter" Value="{Binding EasingFunction}"></Setter> <Setter Property="CommandParameter" Value="{Binding EasingFunction}" />
</Style> </Style>
</MenuItem.Styles> </MenuItem.Styles>
<MenuItem.Icon> <MenuItem.Icon>

View File

@ -8,9 +8,9 @@ namespace Artemis.UI.Screens.ProfileEditor.Properties.Timeline.Keyframes;
public class TimelineKeyframeView : ReactiveUserControl<ITimelineKeyframeViewModel> public class TimelineKeyframeView : ReactiveUserControl<ITimelineKeyframeViewModel>
{ {
private bool _moved;
private TimelinePropertyView? _timelinePropertyView; private TimelinePropertyView? _timelinePropertyView;
private TimelineView? _timelineView; private TimelineView? _timelineView;
private bool _moved;
public TimelineKeyframeView() public TimelineKeyframeView()
{ {
@ -70,7 +70,9 @@ public class TimelineKeyframeView : ReactiveUserControl<ITimelineKeyframeViewMod
// Select the keyframe if the user didn't move // Select the keyframe if the user didn't move
if (!_moved) if (!_moved)
{
ViewModel.Select(e.KeyModifiers.HasFlag(KeyModifiers.Shift), e.KeyModifiers.HasFlag(KeyModifiers.Control)); ViewModel.Select(e.KeyModifiers.HasFlag(KeyModifiers.Shift), e.KeyModifiers.HasFlag(KeyModifiers.Control));
}
else else
{ {
TimeSpan time = ViewModel.GetTimeSpanAtPosition(e.GetPosition(_timelinePropertyView).X); TimeSpan time = ViewModel.GetTimeSpanAtPosition(e.GetPosition(_timelinePropertyView).X);

View File

@ -15,10 +15,10 @@ namespace Artemis.UI.Screens.ProfileEditor.Properties.Timeline.Keyframes;
public class TimelineKeyframeViewModel<T> : ActivatableViewModelBase, ITimelineKeyframeViewModel public class TimelineKeyframeViewModel<T> : ActivatableViewModelBase, ITimelineKeyframeViewModel
{ {
private readonly IProfileEditorService _profileEditorService; private readonly IProfileEditorService _profileEditorService;
private ObservableAsPropertyHelper<bool>? _isSelected;
private string _timestamp;
private double _x; private double _x;
private string _timestamp;
private ObservableAsPropertyHelper<bool>? _isSelected;
public TimelineKeyframeViewModel(LayerPropertyKeyframe<T> layerPropertyKeyframe, IProfileEditorService profileEditorService) public TimelineKeyframeViewModel(LayerPropertyKeyframe<T> layerPropertyKeyframe, IProfileEditorService profileEditorService)
{ {
@ -56,16 +56,16 @@ public class TimelineKeyframeViewModel<T> : ActivatableViewModelBase, ITimelineK
set => RaiseAndSetIfChanged(ref _timestamp, value); set => RaiseAndSetIfChanged(ref _timestamp, value);
} }
public bool IsSelected => _isSelected?.Value ?? false;
public TimeSpan Position => LayerPropertyKeyframe.Position;
public ILayerPropertyKeyframe Keyframe => LayerPropertyKeyframe;
public void Update() public void Update()
{ {
X = _pixelsPerSecond * LayerPropertyKeyframe.Position.TotalSeconds; X = _pixelsPerSecond * LayerPropertyKeyframe.Position.TotalSeconds;
Timestamp = $"{Math.Floor(LayerPropertyKeyframe.Position.TotalSeconds):00}.{LayerPropertyKeyframe.Position.Milliseconds:000}"; Timestamp = $"{Math.Floor(LayerPropertyKeyframe.Position.TotalSeconds):00}.{LayerPropertyKeyframe.Position.Milliseconds:000}";
} }
public bool IsSelected => _isSelected?.Value ?? false;
public TimeSpan Position => LayerPropertyKeyframe.Position;
public ILayerPropertyKeyframe Keyframe => LayerPropertyKeyframe;
/// <inheritdoc /> /// <inheritdoc />
public void Select(bool expand, bool toggle) public void Select(bool expand, bool toggle)
{ {
@ -165,4 +165,3 @@ public class TimelineKeyframeViewModel<T> : ActivatableViewModelBase, ITimelineK
#endregion #endregion
} }

View File

@ -43,7 +43,7 @@
</Button> </Button>
</StackPanel> </StackPanel>
<Rectangle Name="KeyframeDragVisualRight" <Rectangle Name="KeyframeDragVisualRight"
Grid.Column="3" Grid.Column="3"
Classes="resize-visual" /> Classes="resize-visual" />
<Rectangle Name="KeyframeDragAnchor" <Rectangle Name="KeyframeDragAnchor"
@ -52,7 +52,7 @@
PointerPressed="KeyframeDragAnchor_OnPointerPressed" PointerPressed="KeyframeDragAnchor_OnPointerPressed"
PointerMoved="KeyframeDragAnchor_OnPointerMoved" PointerMoved="KeyframeDragAnchor_OnPointerMoved"
PointerReleased="KeyframeDragAnchor_OnPointerReleased" PointerReleased="KeyframeDragAnchor_OnPointerReleased"
ToolTip.Tip="{CompiledBinding EndTimestamp}"/> ToolTip.Tip="{CompiledBinding EndTimestamp}" />
</Grid> </Grid>
</Border> </Border>
</UserControl> </UserControl>

View File

@ -4,47 +4,46 @@ using Avalonia.Input;
using Avalonia.Markup.Xaml; using Avalonia.Markup.Xaml;
using Avalonia.ReactiveUI; using Avalonia.ReactiveUI;
namespace Artemis.UI.Screens.ProfileEditor.Properties.Timeline.Segments namespace Artemis.UI.Screens.ProfileEditor.Properties.Timeline.Segments;
public class EndSegmentView : ReactiveUserControl<EndSegmentViewModel>
{ {
public partial class EndSegmentView : ReactiveUserControl<EndSegmentViewModel> private readonly Rectangle _keyframeDragAnchor;
private double _dragOffset;
public EndSegmentView()
{ {
private readonly Rectangle _keyframeDragAnchor; InitializeComponent();
private double _dragOffset; _keyframeDragAnchor = this.Get<Rectangle>("KeyframeDragAnchor");
}
public EndSegmentView() private void InitializeComponent()
{ {
InitializeComponent(); AvaloniaXamlLoader.Load(this);
_keyframeDragAnchor = this.Get<Rectangle>("KeyframeDragAnchor"); }
}
private void InitializeComponent() private void KeyframeDragAnchor_OnPointerPressed(object? sender, PointerPressedEventArgs e)
{ {
AvaloniaXamlLoader.Load(this); if (ViewModel == null || !e.GetCurrentPoint(this).Properties.IsLeftButtonPressed)
} return;
e.Pointer.Capture(_keyframeDragAnchor);
private void KeyframeDragAnchor_OnPointerPressed(object? sender, PointerPressedEventArgs e) _dragOffset = ViewModel.Width - e.GetCurrentPoint(this).Position.X;
{ ViewModel.StartResize();
if (ViewModel == null || !e.GetCurrentPoint(this).Properties.IsLeftButtonPressed) }
return;
e.Pointer.Capture(_keyframeDragAnchor);
_dragOffset = ViewModel.Width - e.GetCurrentPoint(this).Position.X; private void KeyframeDragAnchor_OnPointerMoved(object? sender, PointerEventArgs e)
ViewModel.StartResize(); {
} if (ViewModel == null || !ReferenceEquals(e.Pointer.Captured, _keyframeDragAnchor))
return;
ViewModel.UpdateResize(e.GetCurrentPoint(this).Position.X + _dragOffset);
}
private void KeyframeDragAnchor_OnPointerMoved(object? sender, PointerEventArgs e) private void KeyframeDragAnchor_OnPointerReleased(object? sender, PointerReleasedEventArgs e)
{ {
if (ViewModel == null || !ReferenceEquals(e.Pointer.Captured, _keyframeDragAnchor)) if (ViewModel == null || !ReferenceEquals(e.Pointer.Captured, _keyframeDragAnchor))
return; return;
ViewModel.UpdateResize(e.GetCurrentPoint(this).Position.X + _dragOffset); e.Pointer.Capture(null);
} ViewModel.FinishResize(e.GetCurrentPoint(this).Position.X + _dragOffset);
private void KeyframeDragAnchor_OnPointerReleased(object? sender, PointerReleasedEventArgs e)
{
if (ViewModel == null || !ReferenceEquals(e.Pointer.Captured, _keyframeDragAnchor))
return;
e.Pointer.Capture(null);
ViewModel.FinishResize(e.GetCurrentPoint(this).Position.X + _dragOffset);
}
} }
} }

View File

@ -1,6 +1,4 @@
using System; using System;
using System.Collections.Generic;
using System.Linq;
using System.Reactive.Linq; using System.Reactive.Linq;
using Artemis.Core; using Artemis.Core;
using Artemis.UI.Shared.Services.ProfileEditor; using Artemis.UI.Shared.Services.ProfileEditor;
@ -13,14 +11,14 @@ namespace Artemis.UI.Screens.ProfileEditor.Properties.Timeline.Segments;
public class EndSegmentViewModel : TimelineSegmentViewModel public class EndSegmentViewModel : TimelineSegmentViewModel
{ {
private readonly IProfileEditorService _profileEditorService; private readonly IProfileEditorService _profileEditorService;
private RenderProfileElement? _profileElement; private readonly ObservableAsPropertyHelper<double> _width;
private int _pixelsPerSecond;
private TimeSpan _time;
private ObservableAsPropertyHelper<double>? _start;
private ObservableAsPropertyHelper<double>? _end; private ObservableAsPropertyHelper<double>? _end;
private ObservableAsPropertyHelper<string?>? _endTimestamp; private ObservableAsPropertyHelper<string?>? _endTimestamp;
private readonly ObservableAsPropertyHelper<double> _width;
private TimeSpan _initialLength; private TimeSpan _initialLength;
private int _pixelsPerSecond;
private RenderProfileElement? _profileElement;
private ObservableAsPropertyHelper<double>? _start;
private TimeSpan _time;
public EndSegmentViewModel(IProfileEditorService profileEditorService) : base(profileEditorService) public EndSegmentViewModel(IProfileEditorService profileEditorService) : base(profileEditorService)
@ -59,6 +57,7 @@ public class EndSegmentViewModel : TimelineSegmentViewModel
public override double StartX => _start?.Value ?? 0; public override double StartX => _start?.Value ?? 0;
public override TimeSpan End => _profileElement?.Timeline.EndSegmentEndPosition ?? TimeSpan.Zero; public override TimeSpan End => _profileElement?.Timeline.EndSegmentEndPosition ?? TimeSpan.Zero;
public override double EndX => _end?.Value ?? 0; public override double EndX => _end?.Value ?? 0;
public override TimeSpan Length public override TimeSpan Length
{ {
get => _profileElement?.Timeline.EndSegmentLength ?? TimeSpan.Zero; get => _profileElement?.Timeline.EndSegmentLength ?? TimeSpan.Zero;

View File

@ -58,7 +58,7 @@
<avalonia:MaterialIcon Kind="PlusCircle" /> <avalonia:MaterialIcon Kind="PlusCircle" />
</Button> </Button>
<Rectangle Name="KeyframeDragVisual" <Rectangle Name="KeyframeDragVisual"
Grid.Column="4" Grid.Column="4"
Classes="resize-visual" /> Classes="resize-visual" />
<Rectangle Name="KeyframeDragAnchor" <Rectangle Name="KeyframeDragAnchor"
@ -67,7 +67,7 @@
PointerPressed="KeyframeDragAnchor_OnPointerPressed" PointerPressed="KeyframeDragAnchor_OnPointerPressed"
PointerMoved="KeyframeDragAnchor_OnPointerMoved" PointerMoved="KeyframeDragAnchor_OnPointerMoved"
PointerReleased="KeyframeDragAnchor_OnPointerReleased" PointerReleased="KeyframeDragAnchor_OnPointerReleased"
ToolTip.Tip="{CompiledBinding EndTimestamp}"/> ToolTip.Tip="{CompiledBinding EndTimestamp}" />
</Grid> </Grid>
</Border> </Border>
</UserControl> </UserControl>

View File

@ -4,59 +4,58 @@ using Avalonia.Input;
using Avalonia.Markup.Xaml; using Avalonia.Markup.Xaml;
using Avalonia.ReactiveUI; using Avalonia.ReactiveUI;
namespace Artemis.UI.Screens.ProfileEditor.Properties.Timeline.Segments namespace Artemis.UI.Screens.ProfileEditor.Properties.Timeline.Segments;
public class MainSegmentView : ReactiveUserControl<MainSegmentViewModel>
{ {
public partial class MainSegmentView : ReactiveUserControl<MainSegmentViewModel> private readonly Rectangle _keyframeDragAnchor;
private double _dragOffset;
public MainSegmentView()
{ {
private readonly Rectangle _keyframeDragAnchor; InitializeComponent();
private double _dragOffset; _keyframeDragAnchor = this.Get<Rectangle>("KeyframeDragAnchor");
}
public MainSegmentView() private void InitializeComponent()
{ {
InitializeComponent(); AvaloniaXamlLoader.Load(this);
_keyframeDragAnchor = this.Get<Rectangle>("KeyframeDragAnchor"); }
}
private void InitializeComponent() private void KeyframeDragAnchor_OnPointerPressed(object? sender, PointerPressedEventArgs e)
{ {
AvaloniaXamlLoader.Load(this); if (ViewModel == null || !e.GetCurrentPoint(this).Properties.IsLeftButtonPressed)
} return;
e.Pointer.Capture(_keyframeDragAnchor);
private void KeyframeDragAnchor_OnPointerPressed(object? sender, PointerPressedEventArgs e) _dragOffset = ViewModel.Width - e.GetCurrentPoint(this).Position.X;
{ ViewModel.StartResize();
if (ViewModel == null || !e.GetCurrentPoint(this).Properties.IsLeftButtonPressed) }
return;
e.Pointer.Capture(_keyframeDragAnchor);
_dragOffset = ViewModel.Width - e.GetCurrentPoint(this).Position.X; private void KeyframeDragAnchor_OnPointerMoved(object? sender, PointerEventArgs e)
ViewModel.StartResize(); {
} if (ViewModel == null || !ReferenceEquals(e.Pointer.Captured, _keyframeDragAnchor))
return;
ViewModel.UpdateResize(e.GetCurrentPoint(this).Position.X + _dragOffset);
}
private void KeyframeDragAnchor_OnPointerMoved(object? sender, PointerEventArgs e) private void KeyframeDragAnchor_OnPointerReleased(object? sender, PointerReleasedEventArgs e)
{ {
if (ViewModel == null || !ReferenceEquals(e.Pointer.Captured, _keyframeDragAnchor)) if (ViewModel == null || !ReferenceEquals(e.Pointer.Captured, _keyframeDragAnchor))
return; return;
ViewModel.UpdateResize(e.GetCurrentPoint(this).Position.X + _dragOffset); e.Pointer.Capture(null);
} ViewModel.FinishResize(e.GetCurrentPoint(this).Position.X + _dragOffset);
}
private void KeyframeDragAnchor_OnPointerReleased(object? sender, PointerReleasedEventArgs e) private void KeyframeDragStartAnchor_OnPointerPressed(object? sender, PointerPressedEventArgs e)
{ {
if (ViewModel == null || !ReferenceEquals(e.Pointer.Captured, _keyframeDragAnchor)) }
return;
e.Pointer.Capture(null);
ViewModel.FinishResize(e.GetCurrentPoint(this).Position.X + _dragOffset);
}
private void KeyframeDragStartAnchor_OnPointerPressed(object? sender, PointerPressedEventArgs e) private void KeyframeDragStartAnchor_OnPointerMoved(object? sender, PointerEventArgs e)
{ {
} }
private void KeyframeDragStartAnchor_OnPointerMoved(object? sender, PointerEventArgs e) private void KeyframeDragStartAnchor_OnPointerReleased(object? sender, PointerReleasedEventArgs e)
{ {
}
private void KeyframeDragStartAnchor_OnPointerReleased(object? sender, PointerReleasedEventArgs e)
{
}
} }
} }

View File

@ -6,23 +6,22 @@
</Border> </Border>
</Design.PreviewWith> </Design.PreviewWith>
<Style Selector="ContentControl.segment-content-control"> <Style Selector="ContentControl.segment-content-control" />
</Style>
<Style Selector="Border.segment-container"> <Style Selector="Border.segment-container">
<Setter Property="Height" Value="18"></Setter> <Setter Property="Height" Value="18" />
</Style> </Style>
<Style Selector="Rectangle.resize-visual"> <Style Selector="Rectangle.resize-visual">
<Setter Property="Fill" Value="{DynamicResource SystemAccentColorLight2}"/> <Setter Property="Fill" Value="{DynamicResource SystemAccentColorLight2}" />
<Setter Property="Width" Value="4"/> <Setter Property="Width" Value="4" />
<Setter Property="HorizontalAlignment" Value="Right"/> <Setter Property="HorizontalAlignment" Value="Right" />
</Style> </Style>
<Style Selector="Rectangle.resize-anchor"> <Style Selector="Rectangle.resize-anchor">
<Setter Property="Fill" Value="Transparent"/> <Setter Property="Fill" Value="Transparent" />
<Setter Property="Width" Value="10"/> <Setter Property="Width" Value="10" />
<Setter Property="HorizontalAlignment" Value="Right"/> <Setter Property="HorizontalAlignment" Value="Right" />
<Setter Property="Cursor" Value="SizeWestEast"/> <Setter Property="Cursor" Value="SizeWestEast" />
</Style> </Style>
</Styles> </Styles>

View File

@ -2,7 +2,6 @@
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:system="clr-namespace:System;assembly=System.Runtime"
xmlns:avalonia="clr-namespace:Material.Icons.Avalonia;assembly=Material.Icons.Avalonia" xmlns:avalonia="clr-namespace:Material.Icons.Avalonia;assembly=Material.Icons.Avalonia"
xmlns:segments="clr-namespace:Artemis.UI.Screens.ProfileEditor.Properties.Timeline.Segments" xmlns:segments="clr-namespace:Artemis.UI.Screens.ProfileEditor.Properties.Timeline.Segments"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="18" mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="18"
@ -49,7 +48,7 @@
PointerPressed="KeyframeDragAnchor_OnPointerPressed" PointerPressed="KeyframeDragAnchor_OnPointerPressed"
PointerMoved="KeyframeDragAnchor_OnPointerMoved" PointerMoved="KeyframeDragAnchor_OnPointerMoved"
PointerReleased="KeyframeDragAnchor_OnPointerReleased" PointerReleased="KeyframeDragAnchor_OnPointerReleased"
ToolTip.Tip="{CompiledBinding EndTimestamp}"/> ToolTip.Tip="{CompiledBinding EndTimestamp}" />
</Grid> </Grid>
</Border> </Border>

View File

@ -4,47 +4,46 @@ using Avalonia.Input;
using Avalonia.Markup.Xaml; using Avalonia.Markup.Xaml;
using Avalonia.ReactiveUI; using Avalonia.ReactiveUI;
namespace Artemis.UI.Screens.ProfileEditor.Properties.Timeline.Segments namespace Artemis.UI.Screens.ProfileEditor.Properties.Timeline.Segments;
public class StartSegmentView : ReactiveUserControl<StartSegmentViewModel>
{ {
public partial class StartSegmentView : ReactiveUserControl<StartSegmentViewModel> private readonly Rectangle _keyframeDragAnchor;
private double _dragOffset;
public StartSegmentView()
{ {
private readonly Rectangle _keyframeDragAnchor; InitializeComponent();
private double _dragOffset; _keyframeDragAnchor = this.Get<Rectangle>("KeyframeDragAnchor");
}
public StartSegmentView() private void InitializeComponent()
{ {
InitializeComponent(); AvaloniaXamlLoader.Load(this);
_keyframeDragAnchor = this.Get<Rectangle>("KeyframeDragAnchor"); }
}
private void InitializeComponent() private void KeyframeDragAnchor_OnPointerPressed(object? sender, PointerPressedEventArgs e)
{ {
AvaloniaXamlLoader.Load(this); if (ViewModel == null || !e.GetCurrentPoint(this).Properties.IsLeftButtonPressed)
} return;
e.Pointer.Capture(_keyframeDragAnchor);
private void KeyframeDragAnchor_OnPointerPressed(object? sender, PointerPressedEventArgs e) _dragOffset = ViewModel.Width - e.GetCurrentPoint(this).Position.X;
{ ViewModel.StartResize();
if (ViewModel == null || !e.GetCurrentPoint(this).Properties.IsLeftButtonPressed) }
return;
e.Pointer.Capture(_keyframeDragAnchor);
_dragOffset = ViewModel.Width - e.GetCurrentPoint(this).Position.X; private void KeyframeDragAnchor_OnPointerMoved(object? sender, PointerEventArgs e)
ViewModel.StartResize(); {
} if (ViewModel == null || !ReferenceEquals(e.Pointer.Captured, _keyframeDragAnchor))
return;
ViewModel.UpdateResize(e.GetCurrentPoint(this).Position.X + _dragOffset);
}
private void KeyframeDragAnchor_OnPointerMoved(object? sender, PointerEventArgs e) private void KeyframeDragAnchor_OnPointerReleased(object? sender, PointerReleasedEventArgs e)
{ {
if (ViewModel == null || !ReferenceEquals(e.Pointer.Captured, _keyframeDragAnchor)) if (ViewModel == null || !ReferenceEquals(e.Pointer.Captured, _keyframeDragAnchor))
return; return;
ViewModel.UpdateResize(e.GetCurrentPoint(this).Position.X + _dragOffset); e.Pointer.Capture(null);
} ViewModel.FinishResize(e.GetCurrentPoint(this).Position.X + _dragOffset);
private void KeyframeDragAnchor_OnPointerReleased(object? sender, PointerReleasedEventArgs e)
{
if (ViewModel == null || !ReferenceEquals(e.Pointer.Captured, _keyframeDragAnchor))
return;
e.Pointer.Capture(null);
ViewModel.FinishResize(e.GetCurrentPoint(this).Position.X + _dragOffset);
}
} }
} }

View File

@ -1,12 +1,9 @@
using System; using System;
using System.Collections.Generic;
using System.Linq;
using System.Reactive.Linq; using System.Reactive.Linq;
using Artemis.Core; using Artemis.Core;
using Artemis.UI.Shared.Services.ProfileEditor; using Artemis.UI.Shared.Services.ProfileEditor;
using Artemis.UI.Shared.Services.ProfileEditor.Commands; using Artemis.UI.Shared.Services.ProfileEditor.Commands;
using Avalonia.Controls.Mixins; using Avalonia.Controls.Mixins;
using Castle.DynamicProxy.Generators;
using ReactiveUI; using ReactiveUI;
namespace Artemis.UI.Screens.ProfileEditor.Properties.Timeline.Segments; namespace Artemis.UI.Screens.ProfileEditor.Properties.Timeline.Segments;
@ -14,13 +11,13 @@ namespace Artemis.UI.Screens.ProfileEditor.Properties.Timeline.Segments;
public class StartSegmentViewModel : TimelineSegmentViewModel public class StartSegmentViewModel : TimelineSegmentViewModel
{ {
private readonly IProfileEditorService _profileEditorService; private readonly IProfileEditorService _profileEditorService;
private RenderProfileElement? _profileElement; private readonly ObservableAsPropertyHelper<double> _width;
private int _pixelsPerSecond;
private TimeSpan _time;
private ObservableAsPropertyHelper<double>? _end; private ObservableAsPropertyHelper<double>? _end;
private ObservableAsPropertyHelper<string?>? _endTimestamp; private ObservableAsPropertyHelper<string?>? _endTimestamp;
private readonly ObservableAsPropertyHelper<double> _width;
private TimeSpan _initialLength; private TimeSpan _initialLength;
private int _pixelsPerSecond;
private RenderProfileElement? _profileElement;
private TimeSpan _time;
public StartSegmentViewModel(IProfileEditorService profileEditorService) : base(profileEditorService) public StartSegmentViewModel(IProfileEditorService profileEditorService) : base(profileEditorService)
{ {
@ -52,6 +49,7 @@ public class StartSegmentViewModel : TimelineSegmentViewModel
public override double StartX => 0; public override double StartX => 0;
public override TimeSpan End => _profileElement?.Timeline.StartSegmentEndPosition ?? TimeSpan.Zero; public override TimeSpan End => _profileElement?.Timeline.StartSegmentEndPosition ?? TimeSpan.Zero;
public override double EndX => _end?.Value ?? 0; public override double EndX => _end?.Value ?? 0;
public override TimeSpan Length public override TimeSpan Length
{ {
get => _profileElement?.Timeline.StartSegmentLength ?? TimeSpan.Zero; get => _profileElement?.Timeline.StartSegmentLength ?? TimeSpan.Zero;

View File

@ -15,14 +15,14 @@ public abstract class TimelineSegmentViewModel : ActivatableViewModelBase
{ {
private static readonly TimeSpan NewSegmentLength = TimeSpan.FromSeconds(2); private static readonly TimeSpan NewSegmentLength = TimeSpan.FromSeconds(2);
private readonly IProfileEditorService _profileEditorService; private readonly IProfileEditorService _profileEditorService;
private RenderProfileElement? _profileElement; private TimeSpan _initialLength;
private readonly Dictionary<ILayerPropertyKeyframe, TimeSpan> _originalKeyframePositions = new();
private int _pixelsPerSecond; private int _pixelsPerSecond;
private Dictionary<ILayerPropertyKeyframe, TimeSpan> _originalKeyframePositions = new(); private RenderProfileElement? _profileElement;
private ObservableAsPropertyHelper<bool>? _showAddEnd;
private ObservableAsPropertyHelper<bool>? _showAddMain;
private ObservableAsPropertyHelper<bool>? _showAddStart; private ObservableAsPropertyHelper<bool>? _showAddStart;
private ObservableAsPropertyHelper<bool>? _showAddMain;
private ObservableAsPropertyHelper<bool>? _showAddEnd;
private TimeSpan _initialLength;
protected TimelineSegmentViewModel(IProfileEditorService profileEditorService) protected TimelineSegmentViewModel(IProfileEditorService profileEditorService)
{ {
@ -136,8 +136,10 @@ public abstract class TimelineSegmentViewModel : ActivatableViewModelBase
// Delete keyframes in the segment // Delete keyframes in the segment
foreach (ILayerPropertyKeyframe layerPropertyKeyframe in keyframes) foreach (ILayerPropertyKeyframe layerPropertyKeyframe in keyframes)
{
if (layerPropertyKeyframe.Position > Start && layerPropertyKeyframe.Position <= End) if (layerPropertyKeyframe.Position > Start && layerPropertyKeyframe.Position <= End)
_profileEditorService.ExecuteCommand(new DeleteKeyframe(layerPropertyKeyframe)); _profileEditorService.ExecuteCommand(new DeleteKeyframe(layerPropertyKeyframe));
}
// Move keyframes after the segment forwards // Move keyframes after the segment forwards
ShiftKeyframes(keyframes.Where(s => s.Position > End), new TimeSpan(Length.Ticks * -1)); ShiftKeyframes(keyframes.Where(s => s.Position > End), new TimeSpan(Length.Ticks * -1));

View File

@ -8,13 +8,11 @@
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450" mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="Artemis.UI.Screens.ProfileEditor.Properties.Timeline.TimelineView" x:Class="Artemis.UI.Screens.ProfileEditor.Properties.Timeline.TimelineView"
x:DataType="timeline:TimelineViewModel"> x:DataType="timeline:TimelineViewModel">
<UserControl.Resources> <UserControl.Resources>
<x:Double x:Key="RailsHeight">28</x:Double> </UserControl.Resources>
<x:Double x:Key="RailsBorderHeight">29</x:Double>
</UserControl.Resources>
<Grid Background="Transparent" PointerReleased="InputElement_OnPointerReleased" Focusable="True" MinWidth="{Binding MinWidth}"> <Grid Background="Transparent" PointerReleased="InputElement_OnPointerReleased" Focusable="True" MinWidth="{Binding MinWidth}">
<Grid.KeyBindings> <Grid.KeyBindings>
<KeyBinding Command="{Binding DeleteKeyframes}" Gesture="Delete"/> <KeyBinding Command="{Binding DeleteKeyframes}" Gesture="Delete" />
</Grid.KeyBindings> </Grid.KeyBindings>
<ItemsControl Items="{CompiledBinding PropertyGroupViewModels}"> <ItemsControl Items="{CompiledBinding PropertyGroupViewModels}">
<ItemsControl.ItemTemplate> <ItemsControl.ItemTemplate>
@ -23,8 +21,8 @@
</TreeDataTemplate> </TreeDataTemplate>
</ItemsControl.ItemTemplate> </ItemsControl.ItemTemplate>
</ItemsControl> </ItemsControl>
<shared:SelectionRectangle Name="SelectionRectangle" InputElement="{CompiledBinding $parent}" SelectionFinished="SelectionRectangle_OnSelectionFinished"></shared:SelectionRectangle> <shared:SelectionRectangle Name="SelectionRectangle" InputElement="{CompiledBinding $parent}" SelectionFinished="SelectionRectangle_OnSelectionFinished" />
</Grid> </Grid>
</UserControl> </UserControl>

View File

@ -3,12 +3,10 @@ using System.Collections.Generic;
using System.Collections.ObjectModel; using System.Collections.ObjectModel;
using System.Linq; using System.Linq;
using System.Reactive.Linq; using System.Reactive.Linq;
using Artemis.UI.Ninject.Factories;
using Artemis.UI.Screens.ProfileEditor.Properties.Timeline.Keyframes; using Artemis.UI.Screens.ProfileEditor.Properties.Timeline.Keyframes;
using Artemis.UI.Screens.ProfileEditor.Properties.Timeline.Segments; using Artemis.UI.Screens.ProfileEditor.Properties.Timeline.Segments;
using Artemis.UI.Shared; using Artemis.UI.Shared;
using Artemis.UI.Shared.Services.ProfileEditor; using Artemis.UI.Shared.Services.ProfileEditor;
using Artemis.UI.Shared.Services.ProfileEditor.Commands;
using Avalonia.Controls.Mixins; using Avalonia.Controls.Mixins;
using ReactiveUI; using ReactiveUI;
@ -18,9 +16,9 @@ public class TimelineViewModel : ActivatableViewModelBase
{ {
private readonly IProfileEditorService _profileEditorService; private readonly IProfileEditorService _profileEditorService;
private ObservableAsPropertyHelper<double>? _caretPosition; private ObservableAsPropertyHelper<double>? _caretPosition;
private ObservableAsPropertyHelper<int>? _pixelsPerSecond;
private List<ITimelineKeyframeViewModel>? _moveKeyframes;
private ObservableAsPropertyHelper<double> _minWidth; private ObservableAsPropertyHelper<double> _minWidth;
private List<ITimelineKeyframeViewModel>? _moveKeyframes;
private ObservableAsPropertyHelper<int>? _pixelsPerSecond;
public TimelineViewModel(ObservableCollection<PropertyGroupViewModel> propertyGroupViewModels, public TimelineViewModel(ObservableCollection<PropertyGroupViewModel> propertyGroupViewModels,
StartSegmentViewModel startSegmentViewModel, StartSegmentViewModel startSegmentViewModel,
@ -141,8 +139,10 @@ public class TimelineViewModel : ActivatableViewModelBase
public void DuplicateKeyframes(ITimelineKeyframeViewModel? source = null) public void DuplicateKeyframes(ITimelineKeyframeViewModel? source = null)
{ {
if (source is { IsSelected: false }) if (source is {IsSelected: false})
{
source.Delete(); source.Delete();
}
else else
{ {
List<ITimelineKeyframeViewModel> keyframes = PropertyGroupViewModels.SelectMany(g => g.GetAllKeyframeViewModels(true)).Where(k => k.IsSelected).ToList(); List<ITimelineKeyframeViewModel> keyframes = PropertyGroupViewModels.SelectMany(g => g.GetAllKeyframeViewModels(true)).Where(k => k.IsSelected).ToList();
@ -154,8 +154,10 @@ public class TimelineViewModel : ActivatableViewModelBase
public void CopyKeyframes(ITimelineKeyframeViewModel? source = null) public void CopyKeyframes(ITimelineKeyframeViewModel? source = null)
{ {
if (source is { IsSelected: false }) if (source is {IsSelected: false})
{
source.Copy(); source.Copy();
}
else else
{ {
List<ITimelineKeyframeViewModel> keyframes = PropertyGroupViewModels.SelectMany(g => g.GetAllKeyframeViewModels(true)).Where(k => k.IsSelected).ToList(); List<ITimelineKeyframeViewModel> keyframes = PropertyGroupViewModels.SelectMany(g => g.GetAllKeyframeViewModels(true)).Where(k => k.IsSelected).ToList();
@ -167,8 +169,10 @@ public class TimelineViewModel : ActivatableViewModelBase
public void PasteKeyframes(ITimelineKeyframeViewModel? source = null) public void PasteKeyframes(ITimelineKeyframeViewModel? source = null)
{ {
if (source is { IsSelected: false }) if (source is {IsSelected: false})
{
source.Paste(); source.Paste();
}
else else
{ {
List<ITimelineKeyframeViewModel> keyframes = PropertyGroupViewModels.SelectMany(g => g.GetAllKeyframeViewModels(true)).Where(k => k.IsSelected).ToList(); List<ITimelineKeyframeViewModel> keyframes = PropertyGroupViewModels.SelectMany(g => g.GetAllKeyframeViewModels(true)).Where(k => k.IsSelected).ToList();
@ -181,7 +185,9 @@ public class TimelineViewModel : ActivatableViewModelBase
public void DeleteKeyframes(ITimelineKeyframeViewModel? source = null) public void DeleteKeyframes(ITimelineKeyframeViewModel? source = null)
{ {
if (source is {IsSelected: false}) if (source is {IsSelected: false})
{
source.Delete(); source.Delete();
}
else else
{ {
List<ITimelineKeyframeViewModel> keyframes = PropertyGroupViewModels.SelectMany(g => g.GetAllKeyframeViewModels(true)).Where(k => k.IsSelected).ToList(); List<ITimelineKeyframeViewModel> keyframes = PropertyGroupViewModels.SelectMany(g => g.GetAllKeyframeViewModels(true)).Where(k => k.IsSelected).ToList();

View File

@ -10,6 +10,6 @@
<StackPanel.KeyBindings> <StackPanel.KeyBindings>
<KeyBinding Gesture="Enter" Command="{CompiledBinding Confirm}" /> <KeyBinding Gesture="Enter" Command="{CompiledBinding Confirm}" />
</StackPanel.KeyBindings> </StackPanel.KeyBindings>
<TextBox Name="NameTextBox" Text="{CompiledBinding LayerEffectName}" Watermark="Layer effect name"/> <TextBox Name="NameTextBox" Text="{CompiledBinding LayerEffectName}" Watermark="Layer effect name" />
</StackPanel> </StackPanel>
</UserControl> </UserControl>

View File

@ -7,39 +7,38 @@ using FluentAvalonia.UI.Controls;
using ReactiveUI; using ReactiveUI;
using ReactiveUI.Validation.Extensions; using ReactiveUI.Validation.Extensions;
namespace Artemis.UI.Screens.ProfileEditor.Properties.Tree.ContentDialogs namespace Artemis.UI.Screens.ProfileEditor.Properties.Tree.ContentDialogs;
public class LayerEffectRenameViewModel : ContentDialogViewModelBase
{ {
public class LayerEffectRenameViewModel : ContentDialogViewModelBase private readonly BaseLayerEffect _layerEffect;
private readonly IProfileEditorService _profileEditorService;
private string? _layerEffectName;
public LayerEffectRenameViewModel(IProfileEditorService profileEditorService, BaseLayerEffect layerEffect)
{ {
private readonly IProfileEditorService _profileEditorService; _profileEditorService = profileEditorService;
private readonly BaseLayerEffect _layerEffect; _layerEffect = layerEffect;
private string? _layerEffectName; _layerEffectName = layerEffect.Name;
public LayerEffectRenameViewModel(IProfileEditorService profileEditorService, BaseLayerEffect layerEffect) Confirm = ReactiveCommand.Create(ExecuteConfirm, ValidationContext.Valid);
{ this.ValidationRule(vm => vm.LayerEffectName, categoryName => !string.IsNullOrWhiteSpace(categoryName), "You must specify a valid name");
_profileEditorService = profileEditorService; }
_layerEffect = layerEffect;
_layerEffectName = layerEffect.Name;
Confirm = ReactiveCommand.Create(ExecuteConfirm, ValidationContext.Valid); public string? LayerEffectName
this.ValidationRule(vm => vm.LayerEffectName, categoryName => !string.IsNullOrWhiteSpace(categoryName), "You must specify a valid name"); {
} get => _layerEffectName;
set => RaiseAndSetIfChanged(ref _layerEffectName, value);
}
public string? LayerEffectName public ReactiveCommand<Unit, Unit> Confirm { get; }
{
get => _layerEffectName;
set => this.RaiseAndSetIfChanged(ref _layerEffectName, value);
}
public ReactiveCommand<Unit, Unit> Confirm { get; } private void ExecuteConfirm()
{
if (LayerEffectName == null)
return;
private void ExecuteConfirm() _profileEditorService.ExecuteCommand(new RenameLayerEffect(_layerEffect, LayerEffectName));
{ ContentDialog?.Hide(ContentDialogResult.Primary);
if (LayerEffectName == null)
return;
_profileEditorService.ExecuteCommand(new RenameLayerEffect(_layerEffect, LayerEffectName));
ContentDialog?.Hide(ContentDialogResult.Primary);
}
} }
} }

View File

@ -1,5 +1,4 @@
using System.Reactive; using Artemis.Core;
using Artemis.Core;
using ReactiveUI; using ReactiveUI;
namespace Artemis.UI.Screens.ProfileEditor.Properties.Tree; namespace Artemis.UI.Screens.ProfileEditor.Properties.Tree;

View File

@ -71,10 +71,10 @@
Height="29" Height="29"
ColumnDefinitions="Auto,Auto,Auto,*"> ColumnDefinitions="Auto,Auto,Auto,*">
<shared:ArtemisIcon Grid.Column="0" <shared:ArtemisIcon Grid.Column="0"
Icon="{CompiledBinding LayerBrush.Descriptor.Icon}" Icon="{CompiledBinding LayerBrush.Descriptor.Icon}"
Width="16" Width="16"
Height="16" Height="16"
Margin="0 0 5 0" /> Margin="0 0 5 0" />
<TextBlock Grid.Column="1" <TextBlock Grid.Column="1"
ToolTip.Tip="{CompiledBinding LayerBrush.Descriptor.Description}" ToolTip.Tip="{CompiledBinding LayerBrush.Descriptor.Description}"
Margin="0 5 5 0"> Margin="0 5 5 0">

View File

@ -63,6 +63,19 @@ public class TreeGroupViewModel : ActivatableViewModelBase
public ReactiveCommand<Unit, Unit> RenameEffect { get; } public ReactiveCommand<Unit, Unit> RenameEffect { get; }
public ReactiveCommand<Unit, Unit> DeleteEffect { get; } public ReactiveCommand<Unit, Unit> DeleteEffect { get; }
public double GetDepth()
{
int depth = 0;
LayerPropertyGroup? current = LayerPropertyGroup.Parent;
while (current != null)
{
depth++;
current = current.Parent;
}
return depth;
}
private async Task ExecuteOpenBrushSettings() private async Task ExecuteOpenBrushSettings()
{ {
if (LayerBrush?.ConfigurationDialog is not LayerBrushConfigurationDialog configurationViewModel) if (LayerBrush?.ConfigurationDialog is not LayerBrushConfigurationDialog configurationViewModel)
@ -145,19 +158,6 @@ public class TreeGroupViewModel : ActivatableViewModelBase
_profileEditorService.ExecuteCommand(new RemoveLayerEffect(LayerEffect)); _profileEditorService.ExecuteCommand(new RemoveLayerEffect(LayerEffect));
} }
public double GetDepth()
{
int depth = 0;
LayerPropertyGroup? current = LayerPropertyGroup.Parent;
while (current != null)
{
depth++;
current = current.Parent;
}
return depth;
}
private void CloseViewModels() private void CloseViewModels()
{ {
_effectConfigurationWindowViewModel?.Close(null); _effectConfigurationWindowViewModel?.Close(null);

View File

@ -4,7 +4,6 @@
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:avalonia="clr-namespace:Material.Icons.Avalonia;assembly=Material.Icons.Avalonia" xmlns:avalonia="clr-namespace:Material.Icons.Avalonia;assembly=Material.Icons.Avalonia"
xmlns:converters="clr-namespace:Artemis.UI.Converters" xmlns:converters="clr-namespace:Artemis.UI.Converters"
xmlns:sharedConverters="clr-namespace:Artemis.UI.Shared.Converters;assembly=Artemis.UI.Shared"
xmlns:tree="clr-namespace:Artemis.UI.Screens.ProfileEditor.Properties.Tree" xmlns:tree="clr-namespace:Artemis.UI.Screens.ProfileEditor.Properties.Tree"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450" mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="Artemis.UI.Screens.ProfileEditor.Properties.Tree.TreePropertyView" x:Class="Artemis.UI.Screens.ProfileEditor.Properties.Tree.TreePropertyView"
@ -39,7 +38,7 @@
<ContentControl Grid.Column="2" <ContentControl Grid.Column="2"
Margin="5 0" Margin="5 0"
Content="{Binding PropertyInputViewModel}"/> Content="{Binding PropertyInputViewModel}" />
<Button Grid.Column="3" <Button Grid.Column="3"
Margin="0 0 2 0" Margin="0 0 2 0"

View File

@ -15,9 +15,9 @@ namespace Artemis.UI.Screens.ProfileEditor.Properties.Tree;
internal class TreePropertyViewModel<T> : ActivatableViewModelBase, ITreePropertyViewModel internal class TreePropertyViewModel<T> : ActivatableViewModelBase, ITreePropertyViewModel
{ {
private readonly IProfileEditorService _profileEditorService; private readonly IProfileEditorService _profileEditorService;
private TimeSpan _time;
private ObservableAsPropertyHelper<bool>? _isCurrentlySelected;
private ObservableAsPropertyHelper<bool>? _dataBindingEnabled; private ObservableAsPropertyHelper<bool>? _dataBindingEnabled;
private ObservableAsPropertyHelper<bool>? _isCurrentlySelected;
private TimeSpan _time;
public TreePropertyViewModel(LayerProperty<T> layerProperty, PropertyViewModel propertyViewModel, IProfileEditorService profileEditorService, IPropertyInputService propertyInputService) public TreePropertyViewModel(LayerProperty<T> layerProperty, PropertyViewModel propertyViewModel, IProfileEditorService profileEditorService, IPropertyInputService propertyInputService)
{ {
@ -40,7 +40,6 @@ internal class TreePropertyViewModel<T> : ActivatableViewModelBase, ITreePropert
} }
public bool IsCurrentlySelected => _isCurrentlySelected?.Value ?? false; public bool IsCurrentlySelected => _isCurrentlySelected?.Value ?? false;
public bool DataBindingEnabled => _dataBindingEnabled?.Value ?? false;
public LayerProperty<T> LayerProperty { get; } public LayerProperty<T> LayerProperty { get; }
public PropertyViewModel PropertyViewModel { get; } public PropertyViewModel PropertyViewModel { get; }
public PropertyInputViewModel<T>? PropertyInputViewModel { get; } public PropertyInputViewModel<T>? PropertyInputViewModel { get; }
@ -65,6 +64,8 @@ internal class TreePropertyViewModel<T> : ActivatableViewModelBase, ITreePropert
_profileEditorService.ExecuteCommand(new ResetLayerProperty<T>(LayerProperty)); _profileEditorService.ExecuteCommand(new ResetLayerProperty<T>(LayerProperty));
} }
public bool DataBindingEnabled => _dataBindingEnabled?.Value ?? false;
public ILayerProperty BaseLayerProperty => LayerProperty; public ILayerProperty BaseLayerProperty => LayerProperty;
public double GetDepth() public double GetDepth()

View File

@ -2,7 +2,6 @@
using System.Threading.Tasks; using System.Threading.Tasks;
using Artemis.UI.Shared; using Artemis.UI.Shared;
using Artemis.UI.Shared.LayerBrushes; using Artemis.UI.Shared.LayerBrushes;
using Avalonia.Threading;
namespace Artemis.UI.Screens.ProfileEditor.Properties.Windows; namespace Artemis.UI.Screens.ProfileEditor.Properties.Windows;

View File

@ -2,7 +2,6 @@
using System.Threading.Tasks; using System.Threading.Tasks;
using Artemis.UI.Shared; using Artemis.UI.Shared;
using Artemis.UI.Shared.LayerEffects; using Artemis.UI.Shared.LayerEffects;
using Avalonia.Threading;
namespace Artemis.UI.Screens.ProfileEditor.Properties.Windows; namespace Artemis.UI.Screens.ProfileEditor.Properties.Windows;

View File

@ -8,13 +8,13 @@
x:Class="Artemis.UI.Screens.ProfileEditor.StatusBar.StatusBarView"> x:Class="Artemis.UI.Screens.ProfileEditor.StatusBar.StatusBarView">
<UserControl.Styles> <UserControl.Styles>
<Style Selector="Border.status-message-border"> <Style Selector="Border.status-message-border">
<Setter Property="BorderBrush" Value="{DynamicResource ButtonBorderBrush}"></Setter> <Setter Property="BorderBrush" Value="{DynamicResource ButtonBorderBrush}" />
<Setter Property="BorderThickness" Value="1 0 0 0"></Setter> <Setter Property="BorderThickness" Value="1 0 0 0" />
<Setter Property="Margin" Value="5 2 0 2"></Setter> <Setter Property="Margin" Value="5 2 0 2" />
<Setter Property="Transitions"> <Setter Property="Transitions">
<Setter.Value> <Setter.Value>
<Transitions> <Transitions>
<DoubleTransition Property="Opacity" Duration="0.2"></DoubleTransition> <DoubleTransition Property="Opacity" Duration="0.2" />
</Transitions> </Transitions>
</Setter.Value> </Setter.Value>
</Setter> </Setter>
@ -47,6 +47,6 @@
Maximum="350" Maximum="350"
Width="319" Width="319"
Value="{Binding PixelsPerSecond}" Value="{Binding PixelsPerSecond}"
HorizontalAlignment="Right"/> HorizontalAlignment="Right" />
</Grid> </Grid>
</UserControl> </UserControl>

View File

@ -1,18 +1,17 @@
using Avalonia.Markup.Xaml; using Avalonia.Markup.Xaml;
using Avalonia.ReactiveUI; using Avalonia.ReactiveUI;
namespace Artemis.UI.Screens.ProfileEditor.StatusBar namespace Artemis.UI.Screens.ProfileEditor.StatusBar;
{
public partial class StatusBarView : ReactiveUserControl<StatusBarViewModel>
{
public StatusBarView()
{
InitializeComponent();
}
private void InitializeComponent() public class StatusBarView : ReactiveUserControl<StatusBarViewModel>
{ {
AvaloniaXamlLoader.Load(this); public StatusBarView()
} {
InitializeComponent();
}
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
} }
} }

View File

@ -7,13 +7,13 @@
x:Class="Artemis.UI.Screens.ProfileEditor.VisualEditor.Tools.SelectionAddToolView" ClipToBounds="False"> x:Class="Artemis.UI.Screens.ProfileEditor.VisualEditor.Tools.SelectionAddToolView" ClipToBounds="False">
<Grid> <Grid>
<shared:SelectionRectangle InputElement="{Binding $parent[ZoomBorder]}" <shared:SelectionRectangle InputElement="{Binding $parent[ZoomBorder]}"
BorderBrush="{DynamicResource SystemAccentColor}" BorderBrush="{DynamicResource SystemAccentColor}"
BorderRadius="8" BorderRadius="8"
BorderThickness="2" BorderThickness="2"
SelectionFinished="SelectionRectangle_OnSelectionFinished" SelectionFinished="SelectionRectangle_OnSelectionFinished"
ZoomRatio="{Binding $parent[ZoomBorder].ZoomX}"> ZoomRatio="{Binding $parent[ZoomBorder].ZoomX}">
<shared:SelectionRectangle.Background> <shared:SelectionRectangle.Background>
<SolidColorBrush Color="{DynamicResource SystemAccentColorLight1}" Opacity="0.2"></SolidColorBrush> <SolidColorBrush Color="{DynamicResource SystemAccentColorLight1}" Opacity="0.2" />
</shared:SelectionRectangle.Background> </shared:SelectionRectangle.Background>
</shared:SelectionRectangle> </shared:SelectionRectangle>
</Grid> </Grid>

View File

@ -15,9 +15,9 @@ namespace Artemis.UI.Screens.ProfileEditor.VisualEditor.Tools;
public class SelectionAddToolViewModel : ToolViewModel public class SelectionAddToolViewModel : ToolViewModel
{ {
private readonly ObservableAsPropertyHelper<bool>? _isEnabled;
private readonly IProfileEditorService _profileEditorService; private readonly IProfileEditorService _profileEditorService;
private readonly IRgbService _rgbService; private readonly IRgbService _rgbService;
private readonly ObservableAsPropertyHelper<bool>? _isEnabled;
private Layer? _layer; private Layer? _layer;
/// <inheritdoc /> /// <inheritdoc />
@ -49,15 +49,6 @@ public class SelectionAddToolViewModel : ToolViewModel
/// <inheritdoc /> /// <inheritdoc />
public override string ToolTip => "Add LEDs to the current layer"; public override string ToolTip => "Add LEDs to the current layer";
/// <inheritdoc />
protected override void Dispose(bool disposing)
{
if (disposing)
_isEnabled?.Dispose();
base.Dispose(disposing);
}
public void AddLedsInRectangle(SKRect rect, bool expand, bool inverse) public void AddLedsInRectangle(SKRect rect, bool expand, bool inverse)
{ {
if (_layer == null) if (_layer == null)
@ -80,4 +71,13 @@ public class SelectionAddToolViewModel : ToolViewModel
_profileEditorService.ExecuteCommand(new ChangeLayerLeds(_layer, leds.Distinct().ToList())); _profileEditorService.ExecuteCommand(new ChangeLayerLeds(_layer, leds.Distinct().ToList()));
} }
} }
/// <inheritdoc />
protected override void Dispose(bool disposing)
{
if (disposing)
_isEnabled?.Dispose();
base.Dispose(disposing);
}
} }

View File

@ -9,14 +9,14 @@
ClipToBounds="False"> ClipToBounds="False">
<Grid> <Grid>
<shared:SelectionRectangle InputElement="{Binding $parent[paz:ZoomBorder]}" <shared:SelectionRectangle InputElement="{Binding $parent[paz:ZoomBorder]}"
BorderBrush="{DynamicResource SystemAccentColor}" BorderBrush="{DynamicResource SystemAccentColor}"
BorderRadius="8" BorderRadius="8"
BorderThickness="2" BorderThickness="2"
SelectionFinished="SelectionRectangle_OnSelectionFinished" SelectionFinished="SelectionRectangle_OnSelectionFinished"
ZoomRatio="{Binding $parent[ZoomBorder].ZoomX}"> ZoomRatio="{Binding $parent[ZoomBorder].ZoomX}">
<shared:SelectionRectangle.Background> <shared:SelectionRectangle.Background>
<SolidColorBrush Color="{DynamicResource SystemAccentColorLight1}" Opacity="0.2"></SolidColorBrush> <SolidColorBrush Color="{DynamicResource SystemAccentColorLight1}" Opacity="0.2" />
</shared:SelectionRectangle.Background> </shared:SelectionRectangle.Background>
</shared:SelectionRectangle> </shared:SelectionRectangle>
</Grid> </Grid>
</UserControl> </UserControl>

View File

@ -79,7 +79,7 @@
Canvas.Top="{CompiledBinding ShapeBounds.Top}" Canvas.Top="{CompiledBinding ShapeBounds.Top}"
RenderTransformOrigin="{CompiledBinding RelativeAnchor}"> RenderTransformOrigin="{CompiledBinding RelativeAnchor}">
<Border.RenderTransform> <Border.RenderTransform>
<RotateTransform Angle="{CompiledBinding Rotation}"></RotateTransform> <RotateTransform Angle="{CompiledBinding Rotation}" />
</Border.RenderTransform> </Border.RenderTransform>
<Grid Name="HandleGrid"> <Grid Name="HandleGrid">
<!-- Render these first so that they are always behind the actual shape --> <!-- Render these first so that they are always behind the actual shape -->

View File

@ -3,7 +3,6 @@ using System.Collections.Generic;
using System.Linq; using System.Linq;
using Artemis.Core; using Artemis.Core;
using Artemis.UI.Shared.Extensions; using Artemis.UI.Shared.Extensions;
using Artemis.UI.Shared.Providers;
using Avalonia; using Avalonia;
using Avalonia.Controls; using Avalonia.Controls;
using Avalonia.Controls.Mixins; using Avalonia.Controls.Mixins;
@ -24,6 +23,7 @@ namespace Artemis.UI.Screens.ProfileEditor.VisualEditor.Tools;
public class TransformToolView : ReactiveUserControl<TransformToolViewModel> public class TransformToolView : ReactiveUserControl<TransformToolViewModel>
{ {
private readonly Grid _handleGrid;
private readonly List<Control> _handles = new(); private readonly List<Control> _handles = new();
private readonly Panel _resizeBottomCenter; private readonly Panel _resizeBottomCenter;
private readonly Panel _resizeBottomLeft; private readonly Panel _resizeBottomLeft;
@ -36,7 +36,6 @@ public class TransformToolView : ReactiveUserControl<TransformToolViewModel>
private SKPoint _dragOffset; private SKPoint _dragOffset;
private ZoomBorder? _zoomBorder; private ZoomBorder? _zoomBorder;
private readonly Grid _handleGrid;
public TransformToolView() public TransformToolView()
{ {
@ -358,8 +357,8 @@ public class TransformToolView : ReactiveUserControl<TransformToolViewModel>
float startAngle = CalculateAngleToAnchor(e); float startAngle = CalculateAngleToAnchor(e);
_rotationDragOffset = startAngle - ViewModel.Layer.Transform.Rotation; _rotationDragOffset = startAngle - ViewModel.Layer.Transform.Rotation;
ViewModel.StartRotation(); ViewModel.StartRotation();
ToolTip.SetTip((Control)sender, $"{ViewModel.Layer.Transform.Rotation.CurrentValue:F3}°"); ToolTip.SetTip((Control) sender, $"{ViewModel.Layer.Transform.Rotation.CurrentValue:F3}°");
ToolTip.SetIsOpen((Control)sender, true); ToolTip.SetIsOpen((Control) sender, true);
e.Pointer.Capture((IInputElement?) sender); e.Pointer.Capture((IInputElement?) sender);
e.Handled = true; e.Handled = true;
@ -376,7 +375,7 @@ public class TransformToolView : ReactiveUserControl<TransformToolViewModel>
angle += 360; angle += 360;
ViewModel?.UpdateRotation(angle, e.KeyModifiers.HasFlag(KeyModifiers.Control)); ViewModel?.UpdateRotation(angle, e.KeyModifiers.HasFlag(KeyModifiers.Control));
ToolTip.SetTip((Control)sender, $"{ViewModel.Layer.Transform.Rotation.CurrentValue:F3}°"); ToolTip.SetTip((Control) sender, $"{ViewModel.Layer.Transform.Rotation.CurrentValue:F3}°");
e.Handled = true; e.Handled = true;
} }
@ -387,8 +386,8 @@ public class TransformToolView : ReactiveUserControl<TransformToolViewModel>
return; return;
ViewModel?.FinishRotation(); ViewModel?.FinishRotation();
ToolTip.SetTip((Control)sender, null); ToolTip.SetTip((Control) sender, null);
ToolTip.SetIsOpen((Control)sender, false); ToolTip.SetIsOpen((Control) sender, false);
e.Pointer.Capture(null); e.Pointer.Capture(null);
e.Handled = true; e.Handled = true;

View File

@ -185,8 +185,8 @@ public class TransformToolViewModel : ToolViewModel
// Get a normalized point // Get a normalized point
SKPoint scaled = Layer.GetNormalizedPoint(position, true); SKPoint scaled = Layer.GetNormalizedPoint(position, true);
// Compensate for the anchor // Compensate for the anchor
scaled.X += ((Layer.Transform.AnchorPoint.CurrentValue.X) * (Layer.Transform.Scale.CurrentValue.Width/100f)); scaled.X += Layer.Transform.AnchorPoint.CurrentValue.X * (Layer.Transform.Scale.CurrentValue.Width / 100f);
scaled.Y += ((Layer.Transform.AnchorPoint.CurrentValue.Y) * (Layer.Transform.Scale.CurrentValue.Height/100f)); scaled.Y += Layer.Transform.AnchorPoint.CurrentValue.Y * (Layer.Transform.Scale.CurrentValue.Height / 100f);
_movementPreview.Preview(scaled); _movementPreview.Preview(scaled);
} }

View File

@ -20,7 +20,7 @@
</Canvas> </Canvas>
</VisualBrush.Visual> </VisualBrush.Visual>
</VisualBrush> </VisualBrush>
</UserControl.Resources> </UserControl.Resources>
<Grid> <Grid>
<paz:ZoomBorder Name="ZoomBorder" <paz:ZoomBorder Name="ZoomBorder"
Stretch="None" Stretch="None"
@ -34,11 +34,11 @@
Background="Transparent"> Background="Transparent">
<Grid.Transitions> <Grid.Transitions>
<Transitions> <Transitions>
<TransformOperationsTransition Property="RenderTransform" Duration="0:0:0.2" Easing="CubicEaseOut"/> <TransformOperationsTransition Property="RenderTransform" Duration="0:0:0.2" Easing="CubicEaseOut" />
</Transitions> </Transitions>
</Grid.Transitions> </Grid.Transitions>
<!-- The bottom layer consists of devices --> <!-- The bottom layer consists of devices -->
<ItemsControl Items="{CompiledBinding Devices}" ClipToBounds="False"> <ItemsControl Items="{CompiledBinding Devices}" ClipToBounds="False">
<ItemsControl.Styles> <ItemsControl.Styles>
<Style Selector="ContentPresenter"> <Style Selector="ContentPresenter">
@ -58,7 +58,7 @@
</ItemsControl.ItemTemplate> </ItemsControl.ItemTemplate>
</ItemsControl> </ItemsControl>
<!-- The middle layer contains visualizers --> <!-- The middle layer contains visualizers -->
<ItemsControl Items="{CompiledBinding Visualizers}" ClipToBounds="False" IsVisible="{CompiledBinding !SuspendedEditing}"> <ItemsControl Items="{CompiledBinding Visualizers}" ClipToBounds="False" IsVisible="{CompiledBinding !SuspendedEditing}">
<ItemsControl.Styles> <ItemsControl.Styles>
<Style Selector="ContentPresenter"> <Style Selector="ContentPresenter">
@ -73,7 +73,7 @@
</ItemsControl.ItemsPanel> </ItemsControl.ItemsPanel>
</ItemsControl> </ItemsControl>
<!-- The top layer contains tools --> <!-- The top layer contains tools -->
<ItemsControl Items="{CompiledBinding Tools}" ClipToBounds="False" IsVisible="{CompiledBinding !SuspendedEditing}"> <ItemsControl Items="{CompiledBinding Tools}" ClipToBounds="False" IsVisible="{CompiledBinding !SuspendedEditing}">
<ItemsControl.ItemsPanel> <ItemsControl.ItemsPanel>
<ItemsPanelTemplate> <ItemsPanelTemplate>
@ -85,15 +85,15 @@
</paz:ZoomBorder> </paz:ZoomBorder>
<Border CornerRadius="0 0 8 0" VerticalAlignment="Top" HorizontalAlignment="Left"> <Border CornerRadius="0 0 8 0" VerticalAlignment="Top" HorizontalAlignment="Left">
<Border.Background> <Border.Background>
<SolidColorBrush Color="{DynamicResource CardStrokeColorDefaultSolid}" Opacity="0.65"></SolidColorBrush> <SolidColorBrush Color="{DynamicResource CardStrokeColorDefaultSolid}" Opacity="0.65" />
</Border.Background> </Border.Background>
<StackPanel Orientation="Horizontal" Margin="8"> <StackPanel Orientation="Horizontal" Margin="8">
<shared:ProfileConfigurationIcon ConfigurationIcon="{CompiledBinding ProfileConfiguration.Icon}" <shared:ProfileConfigurationIcon ConfigurationIcon="{CompiledBinding ProfileConfiguration.Icon}"
Foreground="{DynamicResource ToolTipForeground}" Foreground="{DynamicResource ToolTipForeground}"
Width="18" Width="18"
Height="18" Height="18"
Margin="0 0 5 0"></shared:ProfileConfigurationIcon> Margin="0 0 5 0" />
<TextBlock Text="{CompiledBinding ProfileConfiguration.Name}"/> <TextBlock Text="{CompiledBinding ProfileConfiguration.Name}" />
</StackPanel> </StackPanel>
</Border> </Border>
</Grid> </Grid>

View File

@ -5,41 +5,40 @@ using Avalonia.Markup.Xaml;
using Avalonia.Media; using Avalonia.Media;
using Avalonia.ReactiveUI; using Avalonia.ReactiveUI;
namespace Artemis.UI.Screens.ProfileEditor.VisualEditor namespace Artemis.UI.Screens.ProfileEditor.VisualEditor;
public class VisualEditorView : ReactiveUserControl<VisualEditorViewModel>
{ {
public class VisualEditorView : ReactiveUserControl<VisualEditorViewModel> private readonly ZoomBorder _zoomBorder;
public VisualEditorView()
{ {
private readonly ZoomBorder _zoomBorder; InitializeComponent();
public VisualEditorView() _zoomBorder = this.Find<ZoomBorder>("ZoomBorder");
{ _zoomBorder.PropertyChanged += ZoomBorderOnPropertyChanged;
InitializeComponent(); UpdateZoomBorderBackground();
}
_zoomBorder = this.Find<ZoomBorder>("ZoomBorder"); private void ZoomBorderOnPropertyChanged(object? sender, AvaloniaPropertyChangedEventArgs e)
_zoomBorder.PropertyChanged += ZoomBorderOnPropertyChanged; {
if (e.Property.Name == nameof(_zoomBorder.Background))
UpdateZoomBorderBackground(); UpdateZoomBorderBackground();
} }
private void ZoomBorderOnPropertyChanged(object? sender, AvaloniaPropertyChangedEventArgs e) private void UpdateZoomBorderBackground()
{ {
if (e.Property.Name == nameof(_zoomBorder.Background)) if (_zoomBorder.Background is VisualBrush visualBrush)
UpdateZoomBorderBackground(); visualBrush.DestinationRect = new RelativeRect(_zoomBorder.OffsetX * -1, _zoomBorder.OffsetY * -1, 20, 20, RelativeUnit.Absolute);
} }
private void UpdateZoomBorderBackground() private void InitializeComponent()
{ {
if (_zoomBorder.Background is VisualBrush visualBrush) AvaloniaXamlLoader.Load(this);
visualBrush.DestinationRect = new RelativeRect(_zoomBorder.OffsetX * -1, _zoomBorder.OffsetY * -1, 20, 20, RelativeUnit.Absolute); }
}
private void InitializeComponent() private void ZoomBorder_OnZoomChanged(object sender, ZoomChangedEventArgs e)
{ {
AvaloniaXamlLoader.Load(this); UpdateZoomBorderBackground();
}
private void ZoomBorder_OnZoomChanged(object sender, ZoomChangedEventArgs e)
{
UpdateZoomBorderBackground();
}
} }
} }

View File

@ -10,11 +10,11 @@
<UserControl.Styles> <UserControl.Styles>
<Style Selector="Path.layer-visualizer"> <Style Selector="Path.layer-visualizer">
<Setter Property="Stroke" Value="{StaticResource ButtonBorderBrushDisabled}" /> <Setter Property="Stroke" Value="{StaticResource ButtonBorderBrushDisabled}" />
<Setter Property="Transitions"> <Setter Property="Transitions">
<Setter.Value> <Setter.Value>
<Transitions> <Transitions>
<DoubleTransition Property="StrokeThickness" Duration="0:0:0.2" Easing="CubicEaseOut"></DoubleTransition> <DoubleTransition Property="StrokeThickness" Duration="0:0:0.2" Easing="CubicEaseOut" />
<BrushTransition Property="Stroke" Duration="0:0:0.2" Easing="CubicEaseOut"></BrushTransition> <BrushTransition Property="Stroke" Duration="0:0:0.2" Easing="CubicEaseOut" />
</Transitions> </Transitions>
</Setter.Value> </Setter.Value>
</Setter> </Setter>
@ -28,7 +28,7 @@
<Setter Property="Transitions"> <Setter Property="Transitions">
<Setter.Value> <Setter.Value>
<Transitions> <Transitions>
<DoubleTransition Property="StrokeThickness" Duration="0:0:0.2" Easing="CubicEaseOut"></DoubleTransition> <DoubleTransition Property="StrokeThickness" Duration="0:0:0.2" Easing="CubicEaseOut" />
</Transitions> </Transitions>
</Setter.Value> </Setter.Value>
</Setter> </Setter>
@ -43,8 +43,7 @@
Classes.layer-visualizer-unbound-selected="{CompiledBinding Selected}" Classes.layer-visualizer-unbound-selected="{CompiledBinding Selected}"
Data="{CompiledBinding ShapeGeometry, Mode=OneWay}" Data="{CompiledBinding ShapeGeometry, Mode=OneWay}"
StrokeThickness="4" StrokeThickness="4"
StrokeJoin="Round"> StrokeJoin="Round" />
</Path>
<Border Name="LayerVisualizerBounds" <Border Name="LayerVisualizerBounds"
ClipToBounds="True" ClipToBounds="True"
Width="{CompiledBinding LayerBounds.Width, Mode=OneWay}" Width="{CompiledBinding LayerBounds.Width, Mode=OneWay}"
@ -54,8 +53,7 @@
Classes.layer-visualizer-selected="{CompiledBinding Selected}" Classes.layer-visualizer-selected="{CompiledBinding Selected}"
Data="{CompiledBinding ShapeGeometry, Mode=OneWay}" Data="{CompiledBinding ShapeGeometry, Mode=OneWay}"
StrokeThickness="4" StrokeThickness="4"
StrokeJoin="Round"> StrokeJoin="Round" />
</Path>
</Border> </Border>
</Canvas> </Canvas>

View File

@ -12,11 +12,11 @@ using ReactiveUI;
namespace Artemis.UI.Screens.ProfileEditor.VisualEditor.Visualizers; namespace Artemis.UI.Screens.ProfileEditor.VisualEditor.Visualizers;
public partial class LayerShapeVisualizerView : ReactiveUserControl<LayerShapeVisualizerViewModel> public class LayerShapeVisualizerView : ReactiveUserControl<LayerShapeVisualizerViewModel>
{ {
private ZoomBorder? _zoomBorder;
private readonly Path _layerVisualizerUnbound;
private readonly Path _layerVisualizer; private readonly Path _layerVisualizer;
private readonly Path _layerVisualizerUnbound;
private ZoomBorder? _zoomBorder;
public LayerShapeVisualizerView() public LayerShapeVisualizerView()
{ {

View File

@ -15,11 +15,11 @@ namespace Artemis.UI.Screens.ProfileEditor.VisualEditor.Visualizers;
public class LayerShapeVisualizerViewModel : ActivatableViewModelBase, IVisualizerViewModel public class LayerShapeVisualizerViewModel : ActivatableViewModelBase, IVisualizerViewModel
{ {
private ObservableAsPropertyHelper<bool>? _selected;
private Rect _layerBounds; private Rect _layerBounds;
private ObservableAsPropertyHelper<bool>? _selected;
private Geometry? _shapeGeometry;
private double _x; private double _x;
private double _y; private double _y;
private Geometry? _shapeGeometry;
public LayerShapeVisualizerViewModel(Layer layer, IProfileEditorService profileEditorService) public LayerShapeVisualizerViewModel(Layer layer, IProfileEditorService profileEditorService)
{ {
@ -49,7 +49,6 @@ public class LayerShapeVisualizerViewModel : ActivatableViewModelBase, IVisualiz
} }
public Layer Layer { get; } public Layer Layer { get; }
public ProfileElement ProfileElement => Layer;
public bool Selected => _selected?.Value ?? false; public bool Selected => _selected?.Value ?? false;
public Rect LayerBounds public Rect LayerBounds
@ -58,26 +57,12 @@ public class LayerShapeVisualizerViewModel : ActivatableViewModelBase, IVisualiz
private set => RaiseAndSetIfChanged(ref _layerBounds, value); private set => RaiseAndSetIfChanged(ref _layerBounds, value);
} }
public double X
{
get => _x;
set => RaiseAndSetIfChanged(ref _x, value);
}
public double Y
{
get => _y;
set => RaiseAndSetIfChanged(ref _y, value);
}
public Geometry? ShapeGeometry public Geometry? ShapeGeometry
{ {
get => _shapeGeometry; get => _shapeGeometry;
set => RaiseAndSetIfChanged(ref _shapeGeometry, value); set => RaiseAndSetIfChanged(ref _shapeGeometry, value);
} }
public int Order => 2;
private void Update() private void Update()
{ {
UpdateLayerBounds(); UpdateLayerBounds();
@ -103,4 +88,20 @@ public class LayerShapeVisualizerViewModel : ActivatableViewModelBase, IVisualiz
if (ShapeGeometry != null) if (ShapeGeometry != null)
ShapeGeometry.Transform = new MatrixTransform(Layer.GetTransformMatrix(false, true, true, true, LayerBounds.ToSKRect()).ToMatrix()); ShapeGeometry.Transform = new MatrixTransform(Layer.GetTransformMatrix(false, true, true, true, LayerBounds.ToSKRect()).ToMatrix());
} }
public ProfileElement ProfileElement => Layer;
public double X
{
get => _x;
set => RaiseAndSetIfChanged(ref _x, value);
}
public double Y
{
get => _y;
set => RaiseAndSetIfChanged(ref _y, value);
}
public int Order => 2;
} }

View File

@ -9,12 +9,12 @@
ClipToBounds="False"> ClipToBounds="False">
<UserControl.Styles> <UserControl.Styles>
<Style Selector="Path.layer-visualizer"> <Style Selector="Path.layer-visualizer">
<Setter Property="Stroke" Value="{StaticResource SystemAccentColorDark2}" /> <Setter Property="Stroke" Value="{StaticResource SystemAccentColorDark2}" />
<Setter Property="Opacity" Value="0" /> <Setter Property="Opacity" Value="0" />
<Setter Property="Transitions"> <Setter Property="Transitions">
<Setter.Value> <Setter.Value>
<Transitions> <Transitions>
<DoubleTransition Property="Opacity" Duration="0:0:0.2" Easing="CubicEaseOut"></DoubleTransition> <DoubleTransition Property="Opacity" Duration="0:0:0.2" Easing="CubicEaseOut" />
</Transitions> </Transitions>
</Setter.Value> </Setter.Value>
</Setter> </Setter>
@ -31,7 +31,7 @@
StrokeDashArray="6,2" StrokeDashArray="6,2"
StrokeJoin="Round"> StrokeJoin="Round">
<Path.Data> <Path.Data>
<RectangleGeometry Rect="{CompiledBinding LayerBounds}"></RectangleGeometry> <RectangleGeometry Rect="{CompiledBinding LayerBounds}" />
</Path.Data> </Path.Data>
</Path> </Path>
</UserControl> </UserControl>

View File

@ -7,52 +7,50 @@ using Avalonia.Controls.Shapes;
using Avalonia.LogicalTree; using Avalonia.LogicalTree;
using Avalonia.Markup.Xaml; using Avalonia.Markup.Xaml;
using Avalonia.ReactiveUI; using Avalonia.ReactiveUI;
using Avalonia.VisualTree;
namespace Artemis.UI.Screens.ProfileEditor.VisualEditor.Visualizers namespace Artemis.UI.Screens.ProfileEditor.VisualEditor.Visualizers;
public class LayerVisualizerView : ReactiveUserControl<LayerVisualizerViewModel>
{ {
public partial class LayerVisualizerView : ReactiveUserControl<LayerVisualizerViewModel> private readonly Path _layerVisualizer;
private ZoomBorder? _zoomBorder;
public LayerVisualizerView()
{ {
private ZoomBorder? _zoomBorder; InitializeComponent();
private readonly Path _layerVisualizer; _layerVisualizer = this.Get<Path>("LayerVisualizer");
public LayerVisualizerView()
{
InitializeComponent();
_layerVisualizer = this.Get<Path>("LayerVisualizer");
}
#region Overrides of TemplatedControl
/// <inheritdoc />
protected override void OnAttachedToLogicalTree(LogicalTreeAttachmentEventArgs e)
{
_zoomBorder = (ZoomBorder?) this.GetLogicalAncestors().FirstOrDefault(l => l is ZoomBorder);
if (_zoomBorder != null)
_zoomBorder.PropertyChanged += ZoomBorderOnPropertyChanged;
base.OnAttachedToLogicalTree(e);
}
/// <inheritdoc />
protected override void OnDetachedFromLogicalTree(LogicalTreeAttachmentEventArgs e)
{
if (_zoomBorder != null)
_zoomBorder.PropertyChanged -= ZoomBorderOnPropertyChanged;
base.OnDetachedFromLogicalTree(e);
}
private void ZoomBorderOnPropertyChanged(object? sender, AvaloniaPropertyChangedEventArgs e)
{
if (e.Property != ZoomBorder.ZoomXProperty || _zoomBorder == null)
return;
_layerVisualizer.StrokeThickness = Math.Max(1, 4 / _zoomBorder.ZoomX);
}
#endregion
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
}
} }
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
}
#region Overrides of TemplatedControl
/// <inheritdoc />
protected override void OnAttachedToLogicalTree(LogicalTreeAttachmentEventArgs e)
{
_zoomBorder = (ZoomBorder?) this.GetLogicalAncestors().FirstOrDefault(l => l is ZoomBorder);
if (_zoomBorder != null)
_zoomBorder.PropertyChanged += ZoomBorderOnPropertyChanged;
base.OnAttachedToLogicalTree(e);
}
/// <inheritdoc />
protected override void OnDetachedFromLogicalTree(LogicalTreeAttachmentEventArgs e)
{
if (_zoomBorder != null)
_zoomBorder.PropertyChanged -= ZoomBorderOnPropertyChanged;
base.OnDetachedFromLogicalTree(e);
}
private void ZoomBorderOnPropertyChanged(object? sender, AvaloniaPropertyChangedEventArgs e)
{
if (e.Property != ZoomBorder.ZoomXProperty || _zoomBorder == null)
return;
_layerVisualizer.StrokeThickness = Math.Max(1, 4 / _zoomBorder.ZoomX);
}
#endregion
} }

View File

@ -7,14 +7,14 @@ using Artemis.UI.Shared.Services.ProfileEditor;
using Avalonia; using Avalonia;
using Avalonia.Controls.Mixins; using Avalonia.Controls.Mixins;
using ReactiveUI; using ReactiveUI;
using SKRect = SkiaSharp.SKRect; using SkiaSharp;
namespace Artemis.UI.Screens.ProfileEditor.VisualEditor.Visualizers; namespace Artemis.UI.Screens.ProfileEditor.VisualEditor.Visualizers;
public class LayerVisualizerViewModel : ActivatableViewModelBase, IVisualizerViewModel public class LayerVisualizerViewModel : ActivatableViewModelBase, IVisualizerViewModel
{ {
private ObservableAsPropertyHelper<bool>? _selected;
private Rect _layerBounds; private Rect _layerBounds;
private ObservableAsPropertyHelper<bool>? _selected;
private double _x; private double _x;
private double _y; private double _y;
@ -36,7 +36,6 @@ public class LayerVisualizerViewModel : ActivatableViewModelBase, IVisualizerVie
} }
public Layer Layer { get; } public Layer Layer { get; }
public ProfileElement ProfileElement => Layer;
public bool Selected => _selected?.Value ?? false; public bool Selected => _selected?.Value ?? false;
public Rect LayerBounds public Rect LayerBounds
@ -45,6 +44,16 @@ public class LayerVisualizerViewModel : ActivatableViewModelBase, IVisualizerVie
private set => RaiseAndSetIfChanged(ref _layerBounds, value); private set => RaiseAndSetIfChanged(ref _layerBounds, value);
} }
private void Update()
{
SKRect bounds = Layer.GetLayerBounds();
LayerBounds = new Rect(0, 0, bounds.Width, bounds.Height);
X = bounds.Left;
Y = bounds.Top;
}
public ProfileElement ProfileElement => Layer;
public double X public double X
{ {
get => _x; get => _x;
@ -58,12 +67,4 @@ public class LayerVisualizerViewModel : ActivatableViewModelBase, IVisualizerVie
} }
public int Order => 1; public int Order => 1;
private void Update()
{
SKRect bounds = Layer.GetLayerBounds();
LayerBounds = new Rect(0, 0, bounds.Width, bounds.Height);
X = bounds.Left;
Y = bounds.Top;
}
} }

View File

@ -6,13 +6,13 @@
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450" mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="Artemis.UI.Screens.ProfileEditor.ProfileEditorTitleBarView"> x:Class="Artemis.UI.Screens.ProfileEditor.ProfileEditorTitleBarView">
<Grid ColumnDefinitions="Auto,*,Auto"> <Grid ColumnDefinitions="Auto,*,Auto">
<ContentControl Grid.Row="0" Grid.Column="0" Content="{Binding MenuBarViewModel}"></ContentControl> <ContentControl Grid.Row="0" Grid.Column="0" Content="{Binding MenuBarViewModel}" />
<!-- This border enables dragging the window in between the menu and the buttons--> <!-- This border enables dragging the window in between the menu and the buttons-->
<Border Grid.Row="0" Grid.Column="1" VerticalAlignment="Stretch" HorizontalAlignment="Stretch" Background="Transparent" IsHitTestVisible="False" /> <Border Grid.Row="0" Grid.Column="1" VerticalAlignment="Stretch" HorizontalAlignment="Stretch" Background="Transparent" IsHitTestVisible="False" />
<Button Grid.Column="2" Classes="title-bar-button" Command="{Binding ShowDebugger}" HorizontalAlignment="Right" VerticalAlignment="Top"> <Button Grid.Column="2" Classes="title-bar-button" Command="{Binding ShowDebugger}" HorizontalAlignment="Right" VerticalAlignment="Top">
<avalonia:MaterialIcon Kind="Bug"></avalonia:MaterialIcon> <avalonia:MaterialIcon Kind="Bug" />
</Button> </Button>
</Grid> </Grid>
</UserControl> </UserControl>

View File

@ -1,24 +1,22 @@
using Avalonia;
using Avalonia.Controls; using Avalonia.Controls;
using Avalonia.Interactivity; using Avalonia.Interactivity;
using Avalonia.Markup.Xaml; using Avalonia.Markup.Xaml;
namespace Artemis.UI.Screens.ProfileEditor namespace Artemis.UI.Screens.ProfileEditor;
public class ProfileEditorTitleBarView : UserControl
{ {
public partial class ProfileEditorTitleBarView : UserControl public ProfileEditorTitleBarView()
{ {
public ProfileEditorTitleBarView() InitializeComponent();
{ }
InitializeComponent();
}
private void InitializeComponent() private void InitializeComponent()
{ {
AvaloniaXamlLoader.Load(this); AvaloniaXamlLoader.Load(this);
} }
private void MenuItem_OnSubmenuOpened(object? sender, RoutedEventArgs e) private void MenuItem_OnSubmenuOpened(object? sender, RoutedEventArgs e)
{ {
}
} }
} }

View File

@ -2,23 +2,22 @@
using Artemis.UI.Services.Interfaces; using Artemis.UI.Services.Interfaces;
using Artemis.UI.Shared; using Artemis.UI.Shared;
namespace Artemis.UI.Screens.ProfileEditor namespace Artemis.UI.Screens.ProfileEditor;
public class ProfileEditorTitleBarViewModel : ViewModelBase
{ {
public class ProfileEditorTitleBarViewModel : ViewModelBase private readonly IDebugService _debugService;
public ProfileEditorTitleBarViewModel(IDebugService debugService, MenuBarViewModel menuBarViewModel)
{ {
private readonly IDebugService _debugService; MenuBarViewModel = menuBarViewModel;
_debugService = debugService;
}
public ProfileEditorTitleBarViewModel(IDebugService debugService, MenuBarViewModel menuBarViewModel) public MenuBarViewModel MenuBarViewModel { get; }
{
MenuBarViewModel = menuBarViewModel;
_debugService = debugService;
}
public MenuBarViewModel MenuBarViewModel { get; } public void ShowDebugger()
{
public void ShowDebugger() _debugService.ShowDebugger();
{
_debugService.ShowDebugger();
}
} }
} }

View File

@ -3,7 +3,6 @@
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:avalonia="clr-namespace:Material.Icons.Avalonia;assembly=Material.Icons.Avalonia" xmlns:avalonia="clr-namespace:Material.Icons.Avalonia;assembly=Material.Icons.Avalonia"
xmlns:controls="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
xmlns:converters="clr-namespace:Artemis.UI.Converters" xmlns:converters="clr-namespace:Artemis.UI.Converters"
xmlns:profileEditor="clr-namespace:Artemis.UI.Screens.ProfileEditor" xmlns:profileEditor="clr-namespace:Artemis.UI.Screens.ProfileEditor"
xmlns:shared="clr-namespace:Artemis.UI.Shared.Services.ProfileEditor;assembly=Artemis.UI.Shared" xmlns:shared="clr-namespace:Artemis.UI.Shared.Services.ProfileEditor;assembly=Artemis.UI.Shared"
@ -11,7 +10,7 @@
x:Class="Artemis.UI.Screens.ProfileEditor.ProfileEditorView" x:Class="Artemis.UI.Screens.ProfileEditor.ProfileEditorView"
x:DataType="profileEditor:ProfileEditorViewModel"> x:DataType="profileEditor:ProfileEditorViewModel">
<UserControl.Resources> <UserControl.Resources>
<converters:DoubleToGridLengthConverter x:Key="DoubleToGridLengthConverter"></converters:DoubleToGridLengthConverter> <converters:DoubleToGridLengthConverter x:Key="DoubleToGridLengthConverter" />
</UserControl.Resources> </UserControl.Resources>
<UserControl.Styles> <UserControl.Styles>
<Style Selector="Border.suspended-editing"> <Style Selector="Border.suspended-editing">
@ -22,10 +21,10 @@
</Style> </Style>
</UserControl.Styles> </UserControl.Styles>
<UserControl.KeyBindings> <UserControl.KeyBindings>
<KeyBinding Command="{CompiledBinding History.Undo}" Gesture="Ctrl+Z"></KeyBinding> <KeyBinding Command="{CompiledBinding History.Undo}" Gesture="Ctrl+Z" />
<KeyBinding Command="{CompiledBinding History.Redo}" Gesture="Ctrl+Y"></KeyBinding> <KeyBinding Command="{CompiledBinding History.Redo}" Gesture="Ctrl+Y" />
<KeyBinding Command="{CompiledBinding ToggleSuspend}" Gesture="F5"></KeyBinding> <KeyBinding Command="{CompiledBinding ToggleSuspend}" Gesture="F5" />
<KeyBinding Command="{CompiledBinding ToggleAutoSuspend}" Gesture="Shift+F5"></KeyBinding> <KeyBinding Command="{CompiledBinding ToggleAutoSuspend}" Gesture="Shift+F5" />
</UserControl.KeyBindings> </UserControl.KeyBindings>
<UserControl.Styles> <UserControl.Styles>
<Style Selector="GridSplitter.editor-grid-splitter-vertical"> <Style Selector="GridSplitter.editor-grid-splitter-vertical">
@ -46,7 +45,7 @@
<Setter Property="Height" Value="18" /> <Setter Property="Height" Value="18" />
</Style> </Style>
<Style Selector="Window:windows Grid.editor-grid"> <Style Selector="Window:windows Grid.editor-grid">
<Setter Property="Margin" Value="0 0 4 4"></Setter> <Setter Property="Margin" Value="0 0 4 4" />
</Style> </Style>
</UserControl.Styles> </UserControl.Styles>
<Grid Classes="editor-grid"> <Grid Classes="editor-grid">
@ -103,7 +102,7 @@
Press F5 to switch between editor mode and normal mode. Auto-switching can be disabled in the run menu. Press F5 to switch between editor mode and normal mode. Auto-switching can be disabled in the run menu.
</TextBlock> </TextBlock>
</StackPanel> </StackPanel>
</Border> </Border>
</Panel> </Panel>
</Border> </Border>
</Grid> </Grid>
@ -127,7 +126,7 @@
<Border Grid.Row="2" Classes="card card-condensed" Margin="4"> <Border Grid.Row="2" Classes="card card-condensed" Margin="4">
<Panel> <Panel>
<ContentControl Content="{CompiledBinding DisplayConditionScriptViewModel}"></ContentControl> <ContentControl Content="{CompiledBinding DisplayConditionScriptViewModel}" />
<Border Classes="suspended-editing" /> <Border Classes="suspended-editing" />
</Panel> </Panel>
</Border> </Border>

View File

@ -2,23 +2,21 @@ using Avalonia.Interactivity;
using Avalonia.Markup.Xaml; using Avalonia.Markup.Xaml;
using Avalonia.ReactiveUI; using Avalonia.ReactiveUI;
namespace Artemis.UI.Screens.ProfileEditor namespace Artemis.UI.Screens.ProfileEditor;
public class ProfileEditorView : ReactiveUserControl<ProfileEditorViewModel>
{ {
public class ProfileEditorView : ReactiveUserControl<ProfileEditorViewModel> public ProfileEditorView()
{ {
public ProfileEditorView() InitializeComponent();
{ }
InitializeComponent();
}
private void InitializeComponent() private void InitializeComponent()
{ {
AvaloniaXamlLoader.Load(this); AvaloniaXamlLoader.Load(this);
} }
private void MenuItem_OnSubmenuOpened(object? sender, RoutedEventArgs e) private void MenuItem_OnSubmenuOpened(object? sender, RoutedEventArgs e)
{ {
}
} }
} }

View File

@ -97,5 +97,6 @@ public class ProfileEditorViewModel : MainScreenViewModel
private void ExecuteToggleAutoSuspend() private void ExecuteToggleAutoSuspend()
{ {
// TODO
} }
} }

View File

@ -151,18 +151,9 @@ namespace Artemis.UI.Screens.Sidebar
if (!await _windowService.ShowConfirmContentDialog("Delete profile", "Are you sure you want to permanently delete this profile?")) if (!await _windowService.ShowConfirmContentDialog("Delete profile", "Are you sure you want to permanently delete this profile?"))
return; return;
try if (_profileConfiguration.IsBeingEdited)
{ _profileEditorService.ChangeCurrentProfileConfiguration(null);
if (_profileConfiguration.IsBeingEdited) _profileService.RemoveProfileConfiguration(_profileConfiguration);
_profileEditorService.ChangeCurrentProfileConfiguration(null);
_profileService.RemoveProfileConfiguration(_profileConfiguration);
}
catch (Exception e)
{
Console.WriteLine(e);
throw;
}
Close(_profileConfiguration); Close(_profileConfiguration);
} }

View File

@ -5,7 +5,8 @@
xmlns:avalonia="clr-namespace:Material.Icons.Avalonia;assembly=Material.Icons.Avalonia" xmlns:avalonia="clr-namespace:Material.Icons.Avalonia;assembly=Material.Icons.Avalonia"
xmlns:local="clr-namespace:Artemis.UI.Screens.Sidebar" xmlns:local="clr-namespace:Artemis.UI.Screens.Sidebar"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450" mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="Artemis.UI.Screens.Sidebar.SidebarCategoryView"> x:Class="Artemis.UI.Screens.Sidebar.SidebarCategoryView"
x:DataType="local:SidebarCategoryViewModel">
<UserControl.Styles> <UserControl.Styles>
<Style Selector=":is(Button).category-button"> <Style Selector=":is(Button).category-button">
<Setter Property="IsVisible" Value="False" /> <Setter Property="IsVisible" Value="False" />
@ -26,11 +27,14 @@
<Style Selector="Border.fadable.suspended"> <Style Selector="Border.fadable.suspended">
<Setter Property="Opacity" Value="1" /> <Setter Property="Opacity" Value="1" />
</Style> </Style>
<Style Selector=".sidebar-listbox > ListBoxItem">
<Setter Property="Padding" Value="6 0"/>
</Style>
</UserControl.Styles> </UserControl.Styles>
<Grid x:Name="ContainerGrid" Margin="0 8 0 0" RowDefinitions="Auto,*"> <Grid x:Name="ContainerGrid" Margin="0 8 0 0" RowDefinitions="Auto,*">
<Grid Grid.Row="0" Background="Transparent" ColumnDefinitions="Auto,Auto,*,Auto,Auto,Auto"> <Grid Grid.Row="0" Background="Transparent" Margin="0 0 6 0" ColumnDefinitions="Auto,*,Auto,Auto,Auto,Auto">
<avalonia:MaterialIcon Classes.chevron-collapsed="{Binding ShowItems}" <avalonia:MaterialIcon Classes.chevron-collapsed="{CompiledBinding !IsCollapsed}"
Kind="ChevronUp" Kind="ChevronUp"
Grid.Column="0" Grid.Column="0"
Margin="5 0" Margin="5 0"
@ -44,13 +48,14 @@
</avalonia:MaterialIcon> </avalonia:MaterialIcon>
<TextBlock Classes="fadable" <TextBlock Classes="fadable"
Classes.suspended="{Binding IsSuspended}" Classes.suspended="{CompiledBinding IsSuspended}"
Grid.Column="1" Grid.Column="1"
Padding="0 5" Padding="0 5"
FontWeight="SemiBold" FontWeight="SemiBold"
FontSize="13" FontSize="13"
VerticalAlignment="Center" VerticalAlignment="Center"
Text="{Binding ProfileCategory.Name, FallbackValue='Profile name'}" Text="{CompiledBinding ProfileCategory.Name, FallbackValue='Profile name'}"
TextTrimming="CharacterEllipsis"
PointerPressed="Title_OnPointerPressed" PointerPressed="Title_OnPointerPressed"
Background="Transparent"> Background="Transparent">
<TextBlock.Transitions> <TextBlock.Transitions>
@ -61,7 +66,7 @@
</TextBlock> </TextBlock>
<Border Classes="fadable" <Border Classes="fadable"
Classes.suspended="{Binding IsSuspended}" Classes.suspended="{CompiledBinding IsSuspended}"
Grid.Column="1" Grid.Column="1"
BorderBrush="White" BorderBrush="White"
BorderThickness="0.5" BorderThickness="0.5"
@ -77,29 +82,34 @@
Grid.Column="2" Grid.Column="2"
ToolTip.Tip="Edit category" ToolTip.Tip="Edit category"
HorizontalAlignment="Right" HorizontalAlignment="Right"
Command="{Binding EditCategory}"> Command="{CompiledBinding EditCategory}"
Margin="0 0 2 0">
<avalonia:MaterialIcon Kind="Cog" /> <avalonia:MaterialIcon Kind="Cog" />
</Button> </Button>
<ToggleButton Classes="category-button icon-button icon-button-small" <Button Classes="icon-button icon-button-small"
Grid.Column="3" Command="{CompiledBinding ToggleSuspended}"
ToolTip.Tip="Suspend category" Grid.Column="3"
Margin="5 0" ToolTip.Tip="Suspend/resume profile"
IsChecked="{Binding IsSuspended}"> Margin="0 0 2 0">
<avalonia:MaterialIcon Kind="Pause" /> <Panel>
</ToggleButton> <avalonia:MaterialIcon Kind="EyeOff" IsVisible="{CompiledBinding IsSuspended}" />
<avalonia:MaterialIcon Kind="Eye" IsVisible="{CompiledBinding !IsSuspended}" />
</Panel>
</Button>
<Button Classes="category-button icon-button icon-button-small" <Button Classes="category-button icon-button icon-button-small"
Grid.Column="4" Grid.Column="4"
ToolTip.Tip="Add profile" ToolTip.Tip="Add profile"
HorizontalAlignment="Right" HorizontalAlignment="Right"
Command="{Binding AddProfile}"> Command="{CompiledBinding AddProfile}"
Margin="0 0 2 0">
<avalonia:MaterialIcon Kind="Plus" /> <avalonia:MaterialIcon Kind="Plus" />
</Button> </Button>
</Grid> </Grid>
<Border Grid.Row="1"> <Border Grid.Row="1">
<ListBox Classes="sidebar-listbox" <ListBox Classes="sidebar-listbox"
Items="{Binding ProfileConfigurations}" Items="{CompiledBinding ProfileConfigurations}"
SelectedItem="{Binding SelectedProfileConfiguration}" SelectedItem="{CompiledBinding SelectedProfileConfiguration}"
MinHeight="10" MinHeight="10"
ScrollViewer.HorizontalScrollBarVisibility="Disabled" ScrollViewer.HorizontalScrollBarVisibility="Disabled"
ScrollViewer.VerticalScrollBarVisibility="Disabled"> ScrollViewer.VerticalScrollBarVisibility="Disabled">

View File

@ -1,4 +1,5 @@
using Avalonia.Input; using System;
using Avalonia.Input;
using Avalonia.Markup.Xaml; using Avalonia.Markup.Xaml;
using Avalonia.ReactiveUI; using Avalonia.ReactiveUI;
@ -18,8 +19,7 @@ namespace Artemis.UI.Screens.Sidebar
private void Title_OnPointerPressed(object? sender, PointerPressedEventArgs e) private void Title_OnPointerPressed(object? sender, PointerPressedEventArgs e)
{ {
if (ViewModel != null) ViewModel?.ToggleCollapsed.Execute().Subscribe();
ViewModel.ShowItems = !ViewModel.ShowItems;
} }
} }
} }

View File

@ -1,15 +1,19 @@
using System; using System;
using System.Collections.ObjectModel; using System.Collections.ObjectModel;
using System.Linq; using System.Linq;
using System.Reactive;
using System.Reactive.Disposables; using System.Reactive.Disposables;
using System.Reactive.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using Artemis.Core; using Artemis.Core;
using Artemis.Core.Events;
using Artemis.Core.Services; using Artemis.Core.Services;
using Artemis.UI.Ninject.Factories; using Artemis.UI.Ninject.Factories;
using Artemis.UI.Shared; using Artemis.UI.Shared;
using Artemis.UI.Shared.Services; using Artemis.UI.Shared.Services;
using Artemis.UI.Shared.Services.Builders; using Artemis.UI.Shared.Services.Builders;
using Artemis.UI.Shared.Services.ProfileEditor; using Artemis.UI.Shared.Services.ProfileEditor;
using DynamicData;
using ReactiveUI; using ReactiveUI;
namespace Artemis.UI.Screens.Sidebar namespace Artemis.UI.Screens.Sidebar
@ -21,6 +25,8 @@ namespace Artemis.UI.Screens.Sidebar
private readonly ISidebarVmFactory _vmFactory; private readonly ISidebarVmFactory _vmFactory;
private readonly IWindowService _windowService; private readonly IWindowService _windowService;
private SidebarProfileConfigurationViewModel? _selectedProfileConfiguration; private SidebarProfileConfigurationViewModel? _selectedProfileConfiguration;
private ObservableAsPropertyHelper<bool>? _isCollapsed;
private ObservableAsPropertyHelper<bool>? _isSuspended;
public SidebarCategoryViewModel(SidebarViewModel sidebarViewModel, ProfileCategory profileCategory, IProfileService profileService, IWindowService windowService, public SidebarCategoryViewModel(SidebarViewModel sidebarViewModel, ProfileCategory profileCategory, IProfileService profileService, IWindowService windowService,
IProfileEditorService profileEditorService, ISidebarVmFactory vmFactory) IProfileEditorService profileEditorService, ISidebarVmFactory vmFactory)
@ -31,24 +37,59 @@ namespace Artemis.UI.Screens.Sidebar
_vmFactory = vmFactory; _vmFactory = vmFactory;
ProfileCategory = profileCategory; ProfileCategory = profileCategory;
SourceCache<ProfileConfiguration, Guid> profileConfigurations = new(t => t.ProfileId);
if (ShowItems) // Only show items when not collapsed
CreateProfileViewModels(); IObservable<Func<ProfileConfiguration, bool>> profileConfigurationsFilter = this.WhenAnyValue(vm => vm.IsCollapsed).Select(b => new Func<object, bool>(_ => !b));
profileConfigurations.Connect()
.SortBy(c => c.Order)
.Filter(profileConfigurationsFilter)
.Transform(c => _vmFactory.SidebarProfileConfigurationViewModel(_sidebarViewModel, c))
.Bind(out ReadOnlyObservableCollection<SidebarProfileConfigurationViewModel> profileConfigurationViewModels)
.Subscribe();
ProfileConfigurations = profileConfigurationViewModels;
this.WhenActivated(disposables => ToggleCollapsed = ReactiveCommand.Create(ExecuteToggleCollapsed);
ToggleSuspended = ReactiveCommand.Create(ExecuteToggleSuspended);
AddProfile = ReactiveCommand.CreateFromTask(ExecuteAddProfile);
EditCategory = ReactiveCommand.CreateFromTask(ExecuteEditCategory);
this.WhenActivated(d =>
{ {
profileEditorService.ProfileConfiguration // Update the list of profiles whenever the category fires events
.Subscribe(p => SelectedProfileConfiguration = ProfileConfigurations.FirstOrDefault(c => ReferenceEquals(c.ProfileConfiguration, p))) Observable.FromEventPattern<ProfileConfigurationEventArgs>(x => profileCategory.ProfileConfigurationAdded += x, x => profileCategory.ProfileConfigurationAdded -= x)
.DisposeWith(disposables); .Subscribe(e => profileConfigurations.AddOrUpdate(e.EventArgs.ProfileConfiguration))
this.WhenAnyValue(vm => vm.SelectedProfileConfiguration) .DisposeWith(d);
.WhereNotNull() Observable.FromEventPattern<ProfileConfigurationEventArgs>(x => profileCategory.ProfileConfigurationRemoved += x, x => profileCategory.ProfileConfigurationRemoved -= x)
.Subscribe(s => profileEditorService.ChangeCurrentProfileConfiguration(s.ProfileConfiguration)); .Subscribe(e => profileConfigurations.Remove(e.EventArgs.ProfileConfiguration))
.DisposeWith(d);
profileEditorService.ProfileConfiguration.Subscribe(p => SelectedProfileConfiguration = ProfileConfigurations.FirstOrDefault(c => ReferenceEquals(c.ProfileConfiguration, p)))
.DisposeWith(d);
_isCollapsed = ProfileCategory.WhenAnyValue(vm => vm.IsCollapsed).ToProperty(this, vm => vm.IsCollapsed).DisposeWith(d);
_isSuspended = ProfileCategory.WhenAnyValue(vm => vm.IsSuspended).ToProperty(this, vm => vm.IsSuspended).DisposeWith(d);
// Change the current profile configuration when a new one is selected
this.WhenAnyValue(vm => vm.SelectedProfileConfiguration).WhereNotNull().Subscribe(s => profileEditorService.ChangeCurrentProfileConfiguration(s.ProfileConfiguration));
});
profileConfigurations.Edit(updater =>
{
foreach (ProfileConfiguration profileConfiguration in profileCategory.ProfileConfigurations)
updater.AddOrUpdate(profileConfiguration);
}); });
} }
public ReactiveCommand<Unit, Unit> ToggleCollapsed { get; }
public ReactiveCommand<Unit, Unit> ToggleSuspended { get; }
public ReactiveCommand<Unit, Unit> AddProfile { get; }
public ReactiveCommand<Unit, Unit> EditCategory { get; }
public ProfileCategory ProfileCategory { get; } public ProfileCategory ProfileCategory { get; }
public ReadOnlyObservableCollection<SidebarProfileConfigurationViewModel> ProfileConfigurations { get; }
public ObservableCollection<SidebarProfileConfigurationViewModel> ProfileConfigurations { get; } = new(); public bool IsCollapsed => _isCollapsed?.Value ?? false;
public bool IsSuspended => _isSuspended?.Value ?? false;
public SidebarProfileConfigurationViewModel? SelectedProfileConfiguration public SidebarProfileConfigurationViewModel? SelectedProfileConfiguration
{ {
@ -56,34 +97,7 @@ namespace Artemis.UI.Screens.Sidebar
set => RaiseAndSetIfChanged(ref _selectedProfileConfiguration, value); set => RaiseAndSetIfChanged(ref _selectedProfileConfiguration, value);
} }
public bool ShowItems private async Task ExecuteEditCategory()
{
get => !ProfileCategory.IsCollapsed;
set
{
ProfileCategory.IsCollapsed = !value;
if (ProfileCategory.IsCollapsed)
ProfileConfigurations.Clear();
else
CreateProfileViewModels();
_profileService.SaveProfileCategory(ProfileCategory);
this.RaisePropertyChanged(nameof(ShowItems));
}
}
public bool IsSuspended
{
get => ProfileCategory.IsSuspended;
set
{
ProfileCategory.IsSuspended = value;
this.RaisePropertyChanged(nameof(IsSuspended));
_profileService.SaveProfileCategory(ProfileCategory);
}
}
public async Task EditCategory()
{ {
await _windowService.CreateContentDialog() await _windowService.CreateContentDialog()
.WithTitle("Edit category") .WithTitle("Edit category")
@ -97,7 +111,7 @@ namespace Artemis.UI.Screens.Sidebar
_sidebarViewModel.UpdateProfileCategories(); _sidebarViewModel.UpdateProfileCategories();
} }
public async Task AddProfile() private async Task ExecuteAddProfile()
{ {
ProfileConfiguration? result = await _windowService.ShowDialogAsync<ProfileConfigurationEditViewModel, ProfileConfiguration?>( ProfileConfiguration? result = await _windowService.ShowDialogAsync<ProfileConfigurationEditViewModel, ProfileConfiguration?>(
("profileCategory", ProfileCategory), ("profileCategory", ProfileCategory),
@ -106,18 +120,20 @@ namespace Artemis.UI.Screens.Sidebar
if (result != null) if (result != null)
{ {
SidebarProfileConfigurationViewModel viewModel = _vmFactory.SidebarProfileConfigurationViewModel(_sidebarViewModel, result); SidebarProfileConfigurationViewModel viewModel = _vmFactory.SidebarProfileConfigurationViewModel(_sidebarViewModel, result);
ProfileConfigurations.Insert(0, viewModel);
SelectedProfileConfiguration = viewModel; SelectedProfileConfiguration = viewModel;
} }
} }
private void CreateProfileViewModels() private void ExecuteToggleCollapsed()
{ {
ProfileConfigurations.Clear(); ProfileCategory.IsCollapsed = !ProfileCategory.IsCollapsed;
foreach (ProfileConfiguration profileConfiguration in ProfileCategory.ProfileConfigurations.OrderBy(p => p.Order)) _profileService.SaveProfileCategory(ProfileCategory);
ProfileConfigurations.Add(_vmFactory.SidebarProfileConfigurationViewModel(_sidebarViewModel, profileConfiguration)); }
SelectedProfileConfiguration = ProfileConfigurations.FirstOrDefault(i => i.ProfileConfiguration.IsBeingEdited); private void ExecuteToggleSuspended()
{
ProfileCategory.IsSuspended = !ProfileCategory.IsSuspended;
_profileService.SaveProfileCategory(ProfileCategory);
} }
} }
} }

View File

@ -69,11 +69,11 @@
</UserControl.ContextMenu> </UserControl.ContextMenu>
<Grid ColumnDefinitions="Auto,*,Auto,Auto"> <Grid ColumnDefinitions="Auto,*,Auto,Auto">
<shared:ProfileConfigurationIcon Grid.Column="0" <shared:ProfileConfigurationIcon Grid.Column="0"
x:Name="ProfileIcon" x:Name="ProfileIcon"
VerticalAlignment="Center" VerticalAlignment="Center"
ConfigurationIcon="{CompiledBinding ProfileConfiguration.Icon}" ConfigurationIcon="{CompiledBinding ProfileConfiguration.Icon}"
Width="20" Width="20"
Height="20" /> Height="20" />
<TextBlock Grid.Column="1" <TextBlock Grid.Column="1"
x:Name="ProfileName" x:Name="ProfileName"
@ -106,15 +106,18 @@
Classes="icon-button icon-button-small" Classes="icon-button icon-button-small"
Grid.Column="2" Grid.Column="2"
ToolTip.Tip="View properties" ToolTip.Tip="View properties"
HorizontalAlignment="Right"> HorizontalAlignment="Right"
Margin="0 0 2 0">
<avalonia:MaterialIcon Kind="Cog" /> <avalonia:MaterialIcon Kind="Cog" />
</Button> </Button>
<ToggleButton Classes="icon-button icon-button-small" <Button Classes="icon-button icon-button-small"
Grid.Column="3" Command="{CompiledBinding ToggleSuspended}"
ToolTip.Tip="Suspend profile" Grid.Column="3"
Margin="2 0 0 0" ToolTip.Tip="Suspend/resume profile">
IsChecked="{CompiledBinding IsSuspended}"> <Panel>
<avalonia:MaterialIcon Kind="Pause" /> <avalonia:MaterialIcon Kind="EyeOff" IsVisible="{CompiledBinding IsSuspended}" />
</ToggleButton> <avalonia:MaterialIcon Kind="Eye" IsVisible="{CompiledBinding !IsSuspended}" />
</Panel>
</Button>
</Grid> </Grid>
</UserControl> </UserControl>

View File

@ -26,31 +26,22 @@ namespace Artemis.UI.Screens.Sidebar
ProfileConfiguration = profileConfiguration; ProfileConfiguration = profileConfiguration;
EditProfile = ReactiveCommand.CreateFromTask(ExecuteEditProfile); EditProfile = ReactiveCommand.CreateFromTask(ExecuteEditProfile);
ToggleSuspended = ReactiveCommand.Create(ExecuteToggleSuspended);
this.WhenActivated(d => this.WhenActivated(d =>
{ {
_isSuspended = ProfileConfiguration.WhenAnyValue(c => c.IsSuspended) _isSuspended = ProfileConfiguration.WhenAnyValue(c => c.IsSuspended).ToProperty(this, vm => vm.IsSuspended).DisposeWith(d);
.ToProperty(this, vm => vm.IsSuspended)
.DisposeWith(d);
}); });
_profileService.LoadProfileConfigurationIcon(ProfileConfiguration); _profileService.LoadProfileConfigurationIcon(ProfileConfiguration);
} }
public ReactiveCommand<Unit, Unit> EditProfile { get; } public ReactiveCommand<Unit, Unit> EditProfile { get; }
public ReactiveCommand<Unit,Unit> ToggleSuspended { get; }
public bool IsProfileActive => ProfileConfiguration.Profile != null; public bool IsSuspended => _isSuspended?.Value ?? false;
public bool IsSuspended private async Task ExecuteEditProfile()
{
get => _isSuspended?.Value ?? false;
set
{
ProfileConfiguration.IsSuspended = value;
_profileService.SaveProfileCategory(ProfileConfiguration.Category);
}
}
public async Task ExecuteEditProfile()
{ {
ProfileConfiguration? edited = await _windowService.ShowDialogAsync<ProfileConfigurationEditViewModel, ProfileConfiguration?>( ProfileConfiguration? edited = await _windowService.ShowDialogAsync<ProfileConfigurationEditViewModel, ProfileConfiguration?>(
("profileCategory", ProfileConfiguration.Category), ("profileCategory", ProfileConfiguration.Category),
@ -60,5 +51,11 @@ namespace Artemis.UI.Screens.Sidebar
if (edited != null) if (edited != null)
_sidebarViewModel.UpdateProfileCategories(); _sidebarViewModel.UpdateProfileCategories();
} }
private void ExecuteToggleSuspended()
{
ProfileConfiguration.IsSuspended = !ProfileConfiguration.IsSuspended;
_profileService.SaveProfileCategory(ProfileConfiguration.Category);
}
} }
} }