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

@ -33,11 +33,11 @@
</TextBlock> </TextBlock>
</DockPanel> </DockPanel>
<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}"
HorizontalAlignment="Stretch" HorizontalAlignment="Stretch"
VerticalAlignment="Bottom"> VerticalAlignment="Bottom">
Edit condition script Edit condition script
</Button> </Button>

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()
{
AvaloniaXamlLoader.Load(this);
}
private void MenuItem_OnSubmenuOpened(object? sender, RoutedEventArgs e)
{
}
} }
}
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
}
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>
@ -42,7 +42,7 @@
<TextBlock TextWrapping="Wrap" HorizontalAlignment="Center" TextAlignment="Center" Foreground="{DynamicResource TextFillColorSecondary}"> <TextBlock TextWrapping="Wrap" HorizontalAlignment="Center" TextAlignment="Center" Foreground="{DynamicResource TextFillColorSecondary}">
When you enable data bindings you can no longer use keyframes or normal values for this property. When you enable data bindings you can no longer use keyframes or normal values for this property.
</TextBlock> </TextBlock>
<controls:HyperlinkButton HorizontalAlignment="Center" <controls:HyperlinkButton HorizontalAlignment="Center"
NavigateUri="https://wiki.artemis-rgb.com/en/guides/user/profiles/data-bindings" NavigateUri="https://wiki.artemis-rgb.com/en/guides/user/profiles/data-bindings"
Margin="0 10"> Margin="0 10">
Learn more Learn more

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}"
@ -21,17 +21,17 @@
<ListBox.DataTemplates> <ListBox.DataTemplates>
<DataTemplate DataType="{x:Type layerEffects:LayerEffectDescriptor}"> <DataTemplate DataType="{x:Type layerEffects:LayerEffectDescriptor}">
<Grid RowDefinitions="Auto,*" <Grid RowDefinitions="Auto,*"
ColumnDefinitions="Auto,Auto" ColumnDefinitions="Auto,Auto"
Background="Transparent" Background="Transparent"
PointerReleased="InputElement_OnPointerReleased" PointerReleased="InputElement_OnPointerReleased"
Margin="0 4" Margin="0 4"
VerticalAlignment="Center"> VerticalAlignment="Center">
<shared:ArtemisIcon Grid.Column="0" <shared:ArtemisIcon Grid.Column="0"
Grid.RowSpan="2" Grid.RowSpan="2"
Icon="{CompiledBinding Icon}" Icon="{CompiledBinding Icon}"
Width="24" Width="24"
Height="24" Height="24"
VerticalAlignment="Center" VerticalAlignment="Center"
Margin="0 0 15 0" /> Margin="0 0 15 0" />
<TextBlock Grid.Column="1" <TextBlock Grid.Column="1"
Grid.Row="0" Grid.Row="0"
@ -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)
@ -58,5 +58,4 @@ public class AddEffectViewModel : ContentDialogViewModelBase
return data => data.DisplayName.Contains(search, StringComparison.InvariantCultureIgnoreCase) || return data => data.DisplayName.Contains(search, StringComparison.InvariantCultureIgnoreCase) ||
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

@ -16,4 +16,4 @@
Margin="0 0 10 0" /> Margin="0 0 10 0" />
<TextBlock Text="{CompiledBinding Description}" /> <TextBlock Text="{CompiledBinding Description}" />
</StackPanel> </StackPanel>
</UserControl> </UserControl>

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)
{ {
@ -157,12 +157,11 @@ public class TimelineKeyframeViewModel<T> : ActivatableViewModelBase, ITimelineK
.Cast<Easings.Functions>() .Cast<Easings.Functions>()
.Select(e => new TimelineEasingViewModel(e, Keyframe))); .Select(e => new TimelineEasingViewModel(e, Keyframe)));
} }
public void SelectEasingFunction(Easings.Functions easingFunction) public void SelectEasingFunction(Easings.Functions easingFunction)
{ {
_profileEditorService.ExecuteCommand(new ChangeKeyframeEasing(Keyframe, easingFunction)); _profileEditorService.ExecuteCommand(new ChangeKeyframeEasing(Keyframe, easingFunction));
} }
#endregion #endregion
} }

View File

@ -28,7 +28,7 @@
IsVisible="{CompiledBinding ShowAddMain}"> IsVisible="{CompiledBinding ShowAddMain}">
<avalonia:MaterialIcon Kind="PlusCircle" /> <avalonia:MaterialIcon Kind="PlusCircle" />
</Button> </Button>
<StackPanel Grid.Column="2" Orientation="Horizontal" VerticalAlignment="Center" HorizontalAlignment="Center" ClipToBounds="True"> <StackPanel Grid.Column="2" Orientation="Horizontal" VerticalAlignment="Center" HorizontalAlignment="Center" ClipToBounds="True">
<TextBlock Name="SegmentTitle" <TextBlock Name="SegmentTitle"
FontSize="13" FontSize="13"
@ -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;
@ -70,7 +69,7 @@ public class EndSegmentViewModel : TimelineSegmentViewModel
} }
public override double Width => _width.Value; public override double Width => _width.Value;
public override string? EndTimestamp => _endTimestamp?.Value; public override string? EndTimestamp => _endTimestamp?.Value;
public override ResizeTimelineSegment.SegmentType Type => ResizeTimelineSegment.SegmentType.End; public override ResizeTimelineSegment.SegmentType Type => ResizeTimelineSegment.SegmentType.End;
} }

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,13 +16,13 @@ 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,
MainSegmentViewModel mainSegmentViewModel, MainSegmentViewModel mainSegmentViewModel,
EndSegmentViewModel endSegmentViewModel, EndSegmentViewModel endSegmentViewModel,
IProfileEditorService profileEditorService) IProfileEditorService profileEditorService)
{ {
@ -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,14 +71,14 @@
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">
Brush - Brush -
</TextBlock> </TextBlock>
<TextBlock Grid.Column="2" <TextBlock Grid.Column="2"
Text="{CompiledBinding LayerBrush.Descriptor.DisplayName}" Text="{CompiledBinding LayerBrush.Descriptor.DisplayName}"

View File

@ -57,12 +57,25 @@ public class TreeGroupViewModel : ActivatableViewModelBase
public BaseLayerEffect? LayerEffect => PropertyGroupViewModel.LayerEffect; public BaseLayerEffect? LayerEffect => PropertyGroupViewModel.LayerEffect;
public LayerPropertyGroupType GroupType { get; private set; } public LayerPropertyGroupType GroupType { get; private set; }
public ObservableCollection<ViewModelBase>? Children => PropertyGroupViewModel.IsExpanded ? PropertyGroupViewModel.Children : null; public ObservableCollection<ViewModelBase>? Children => PropertyGroupViewModel.IsExpanded ? PropertyGroupViewModel.Children : null;
public ReactiveCommand<Unit, Unit> OpenBrushSettings { get; } public ReactiveCommand<Unit, Unit> OpenBrushSettings { get; }
public ReactiveCommand<Unit, Unit> OpenEffectSettings { get; } public ReactiveCommand<Unit, Unit> OpenEffectSettings { get; }
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"
@ -51,7 +50,7 @@
<avalonia:MaterialIcon Kind="BackupRestore" /> <avalonia:MaterialIcon Kind="BackupRestore" />
</Button> </Button>
<ToggleButton Grid.Column="4" <ToggleButton Grid.Column="4"
Name="DataBindingToggleButton" Name="DataBindingToggleButton"
Classes="icon-button" Classes="icon-button"
Margin="2 0 0 0" Margin="2 0 0 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
@ -57,18 +56,6 @@ public class LayerShapeVisualizerViewModel : ActivatableViewModelBase, IVisualiz
get => _layerBounds; get => _layerBounds;
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
{ {
@ -76,8 +63,6 @@ public class LayerShapeVisualizerViewModel : ActivatableViewModelBase, IVisualiz
set => RaiseAndSetIfChanged(ref _shapeGeometry, value); set => RaiseAndSetIfChanged(ref _shapeGeometry, value);
} }
public int Order => 2;
private void Update() private void Update()
{ {
UpdateLayerBounds(); UpdateLayerBounds();
@ -86,7 +71,7 @@ public class LayerShapeVisualizerViewModel : ActivatableViewModelBase, IVisualiz
ShapeGeometry = new RectangleGeometry(LayerBounds); ShapeGeometry = new RectangleGeometry(LayerBounds);
else else
ShapeGeometry = new EllipseGeometry(LayerBounds); ShapeGeometry = new EllipseGeometry(LayerBounds);
UpdateTransform(); UpdateTransform();
} }
@ -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
@ -44,7 +43,17 @@ public class LayerVisualizerViewModel : ActivatableViewModelBase, IVisualizerVie
get => _layerBounds; get => _layerBounds;
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);
}
} }
} }