Forráskód Böngészése

A lot of onboarding stuff

Krzysztof Krysiński 5 hónapja
szülő
commit
a0830769da

+ 2 - 2
src/PixiEditor.Desktop/Program.cs

@@ -20,12 +20,12 @@ public class Program
             .UsePlatformDetect()
             .With(new Win32PlatformOptions()
             {
-                RenderingMode = new Win32RenderingMode[] { Win32RenderingMode.Vulkan, Win32RenderingMode.Wgl },
+                RenderingMode = new Win32RenderingMode[] { Win32RenderingMode.Vulkan },
                 OverlayPopups = true
             })
             .With(new X11PlatformOptions()
             {
-                RenderingMode = new X11RenderingMode[] { X11RenderingMode.Vulkan, X11RenderingMode.Glx },
+                RenderingMode = new X11RenderingMode[] { X11RenderingMode.Vulkan },
                 OverlayPopups = true,
             })
             .WithDrawie()

+ 19 - 13
src/PixiEditor.UI.Common/Styles/TextStyles.axaml

@@ -2,40 +2,46 @@
         xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
     <Design.PreviewWith>
         <Border Padding="20">
-            <TextBlock Classes="h1"/>
+            <TextBlock Classes="h1" />
         </Border>
     </Design.PreviewWith>
-    
+
     <Style Selector="TextBlock">
-        <Setter Property="VerticalAlignment" Value="Center"/>
+        <Setter Property="VerticalAlignment" Value="Center" />
     </Style>
 
     <Style Selector="TextBlock.h1">
-        <Setter Property="FontSize" Value="{DynamicResource Header1}"/>
+        <Setter Property="FontSize" Value="{DynamicResource Header1}" />
     </Style>
 
     <Style Selector="TextBlock.h2">
-        <Setter Property="FontSize" Value="{DynamicResource Header2}"/>
+        <Setter Property="FontSize" Value="{DynamicResource Header2}" />
     </Style>
 
     <Style Selector="TextBlock.h3">
-        <Setter Property="FontSize" Value="{DynamicResource Header3}"/>
+        <Setter Property="FontSize" Value="{DynamicResource Header3}" />
     </Style>
 
     <Style Selector="TextBlock.h4">
-        <Setter Property="FontSize" Value="{DynamicResource Header4}"/>
+        <Setter Property="FontSize" Value="{DynamicResource Header4}" />
     </Style>
-    
+
     <Style Selector="TextBlock.h5">
-        <Setter Property="FontSize" Value="{DynamicResource Header5}"/>
+        <Setter Property="FontSize" Value="{DynamicResource Header5}" />
     </Style>
 
     <Style Selector="TextBlock.hyperlink">
-        <Setter Property="Foreground" Value="{DynamicResource ThemeForegroundSecondaryBrush}"/>
+        <Setter Property="Foreground" Value="{DynamicResource ThemeForegroundSecondaryBrush}" />
     </Style>
 
     <Style Selector="TextBlock.hyperlink:pointerover">
-        <Setter Property="Foreground" Value="{DynamicResource ThemeForegroundBrush}"/>
-        <Setter Property="Cursor" Value="Hand"/>
+        <Setter Property="Foreground" Value="{DynamicResource ThemeForegroundBrush}" />
+        <Setter Property="Cursor" Value="Hand" />
+    </Style>
+
+    <Style Selector=":is(TextBlock).subtext">
+        <Setter Property="FontSize" Value="12" />
+        <Setter Property="Foreground" Value="{DynamicResource ThemeForegroundLowBrush}" />
+        <Setter Property="TextWrapping" Value="Wrap" />
     </Style>
-</Styles>
+</Styles>

+ 12 - 1
src/PixiEditor/Data/Localization/Languages/en.json

@@ -1009,5 +1009,16 @@
   "XOR_VECTOR_PATH_OP": "XOR",
   "REVERSE_DIFFERENCE_VECTOR_PATH_OP": "Reverse Difference",
   "NO_DOCUMENT_OPEN": "Nothing's here",
-  "EMPTY_DOCUMENT_ACTION_BTN": "Start creating"
+  "EMPTY_DOCUMENT_ACTION_BTN": "Start creating",
+  "ONBOARDING_TITLE": "Welcome to",
+  "ONBOARDING_DESCRIPTION": "Let's set up your workspace!",
+  "ONBOARDING_SKIP_BTN": "Skip",
+  "ONBOARDING_ACTION_BTN": "Let's begin",
+  "ONB_SELECT_PRIMARY_TOOLSET": "Select Your Primary Toolset",
+  "ONB_NEXT_BTN": "Next",
+  "ONB_BACK_BTN": "Previous",
+  "ONB_ANALYTICS": "Anonymous Analytics",
+  "ANALYTICS_INFO_DETAILED": "PixiEditor collects anonymous usage data to improve the app. The data does not contain any personal information. Among other things, PixiEditor tracks the following:\n- Which and how the tools are used\n- How long you've used the app for\n- Which commands are used\n- Performance data\n\n You can opt out of analytics at any time in the settings.",
+  "PRIVACY_POLICY": "Privacy Policy",
+  "ONB_SHORTCUTS": "Select Your Shortcuts"
 }

+ 17 - 1
src/PixiEditor/Helpers/Converters/ImagePathToBitmapConverter.cs

@@ -3,6 +3,7 @@ using System.Globalization;
 using System.IO;
 using System.Reflection;
 using Avalonia;
+using Avalonia.Media;
 using Avalonia.Platform;
 using Avalonia.Svg.Skia;
 using PixiEditor.Helpers.Extensions;
@@ -19,7 +20,7 @@ internal class ImagePathToBitmapConverter : SingleInstanceConverter<ImagePathToB
 
         try
         {
-            return LoadBitmapFromRelativePath(path);
+            return LoadImageFromRelativePath(path);
         }
         catch (FileNotFoundException)
         {
@@ -27,6 +28,21 @@ internal class ImagePathToBitmapConverter : SingleInstanceConverter<ImagePathToB
         }
     }
 
+    public static IImage LoadImageFromRelativePath(string path)
+    {
+        Uri baseUri = new Uri($"avares://{Assembly.GetExecutingAssembly().GetName().Name}");
+        Uri uri = new(baseUri, path);
+        if (!AssetLoader.Exists(uri))
+            throw new FileNotFoundException($"Could not find asset with path {path}");
+
+        if (path.EndsWith(".svg"))
+        {
+            return new SvgImage() { Source = new SvgSource(baseUri) { Path = path } };
+        }
+
+        return new Bitmap(AssetLoader.Open(uri));
+    }
+
     public static Bitmap LoadBitmapFromRelativePath(string path)
     {
         Uri uri = new($"avares://{Assembly.GetExecutingAssembly().FullName}{path}");

+ 1 - 0
src/PixiEditor/Styles/PixiEditor.Controls.axaml

@@ -19,6 +19,7 @@
                 <MergeResourceInclude Source="avares://PixiEditor/Styles/Templates/NodeSocket.axaml"/>
                 <MergeResourceInclude Source="avares://PixiEditor/Styles/Templates/ConnectionView.axaml"/>
                 <MergeResourceInclude Source="avares://PixiEditor/Styles/Templates/NodePicker.axaml"/>
+                <MergeResourceInclude Source="avares://PixiEditor/Styles/Templates/ShortcutsTemplateCard.axaml"/>
             </ResourceDictionary.MergedDictionaries>
         </ResourceDictionary>
     </Styles.Resources>

+ 1 - 0
src/PixiEditor/Styles/PixiEditorPopupTemplate.axaml

@@ -49,6 +49,7 @@
                         <DockPanel>
                             <controls:DialogTitleBar
                                 Name="PART_TitleBar"
+                                IsVisible="{TemplateBinding ShowTitleBar}"
                                 DockPanel.Dock="Top" 
                                 CloseCommand="{TemplateBinding CloseCommand}"
                                 CanMinimize="{TemplateBinding CanMinimize}"

+ 59 - 0
src/PixiEditor/Styles/Templates/ShortcutsTemplateCard.axaml

@@ -0,0 +1,59 @@
+<ResourceDictionary
+    xmlns="https://github.com/avaloniaui"
+    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
+    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
+    xmlns:converters="clr-namespace:PixiEditor.Helpers.Converters"
+    xmlns:ui="clr-namespace:PixiEditor.Extensions.UI;assembly=PixiEditor.Extensions"
+    xmlns:shortcuts="clr-namespace:PixiEditor.Views.Shortcuts">
+
+    <ControlTheme TargetType="shortcuts:ShortcutsTemplateCard" x:Key="{x:Type shortcuts:ShortcutsTemplateCard}">
+        <Setter Property="Width" Value="150" />
+        <Setter Property="Height" Value="150" />
+        <Setter Property="Background" Value="Transparent" />
+        <Setter Property="BorderBrush" Value="{DynamicResource ThemeBorderMidBrush}" />
+        <Setter Property="Template">
+            <ControlTemplate>
+                <Border BorderThickness="1"
+                        Background="{TemplateBinding Background}"
+                        BorderBrush="{TemplateBinding BorderBrush}"
+                        CornerRadius="15">
+                    <Grid>
+                        <Grid.RowDefinitions>
+                            <RowDefinition Height="*" />
+                            <RowDefinition Height="12" />
+                            <RowDefinition Height="Auto" />
+                        </Grid.RowDefinitions>
+                        <Image Grid.Row="0" Grid.RowSpan="2" Name="img" HorizontalAlignment="Center"
+                               VerticalAlignment="Center"
+                               Source="{TemplateBinding Logo, Converter={converters:ImagePathToBitmapConverter}}">
+                            <Image.Transitions>
+                                <Transitions>
+                                    <DoubleTransition Duration="0:0:0.15" Property="Width" />
+                                    <DoubleTransition Duration="0:0:0.15" Property="Height" />
+                                </Transitions>
+                            </Image.Transitions>
+                        </Image>
+                        <TextBlock
+                            Grid.Row="2" HorizontalAlignment="Center" VerticalAlignment="Center"
+                            Margin="0, 0, 0, 10" Padding="0"
+                            Classes="h4"
+                            ui:Translator.Key="{TemplateBinding TemplateName}" />
+                    </Grid>
+                </Border>
+            </ControlTemplate>
+        </Setter>
+        <Style Selector="^ /template/ Border Image">
+            <Setter Property="Width" Value="72" />
+            <Setter Property="Height" Value="72" />
+            <Setter Property="Source" Value="{TemplateBinding Logo, Converter={converters:ImagePathToBitmapConverter}}" />
+        </Style>
+        <Style Selector="^:pointerover /template/ Border Image">
+            <Setter Property="Width" Value="100" />
+            <Setter Property="Height" Value="100" />
+            <Setter Property="Source"
+                    Value="{TemplateBinding HoverLogo, Converter={converters:ImagePathToBitmapConverter}}" />
+        </Style>
+    </ControlTheme>
+
+</ResourceDictionary>

+ 2 - 1
src/PixiEditor/ViewModels/SubViewModels/WindowViewModel.cs

@@ -10,6 +10,7 @@ using PixiEditor.Models.Commands;
 using PixiEditor.Models.Handlers;
 using PixiEditor.UI.Common.Fonts;
 using PixiEditor.ViewModels.Document;
+using PixiEditor.ViewModels.UserPreferences;
 using PixiEditor.Views;
 using PixiEditor.Views.Dialogs;
 using PixiEditor.Views.Windows;
@@ -240,7 +241,7 @@ internal class WindowViewModel : SubViewModel<ViewModelMain>, IWindowHandler
         AnalyticsTrack = true)]
     public void OpenOnboardingWindow()
     {
-        new OnboardingDialog().Show(MainWindow.Current);
+        new OnboardingDialog { DataContext = new OnboardingViewModel() }.ShowDialog(MainWindow.Current);
     }
 
     [Commands_Command.Basic("PixiEditor.Window.OpenShortcutWindow", "OPEN_SHORTCUT_WINDOW", "OPEN_SHORTCUT_WINDOW",

+ 178 - 2
src/PixiEditor/ViewModels/UserPreferences/OnboardingViewModel.cs

@@ -1,14 +1,190 @@
-namespace PixiEditor.ViewModels.UserPreferences;
+using System.Collections.ObjectModel;
+using System.Windows.Input;
+using CommunityToolkit.Mvvm.ComponentModel;
+using CommunityToolkit.Mvvm.Input;
+using PixiEditor.Extensions.Common.Localization;
+using PixiEditor.Models.Commands;
+using PixiEditor.Models.Commands.Templates;
+using PixiEditor.Models.Handlers;
+using PixiEditor.ViewModels.SubViewModels;
+using PixiEditor.ViewModels.UserPreferences.Settings;
+using PixiEditor.Views.Shortcuts;
 
-public class OnboardingViewModel : PixiObservableObject
+namespace PixiEditor.ViewModels.UserPreferences;
+
+internal class OnboardingViewModel : PixiObservableObject
 {
     private int page;
+    private FormStep formStep;
+
     public int Page
     {
         get => page;
         set
         {
+            value = Math.Clamp(value, 0, 5);
             SetProperty(ref page, value);
         }
     }
+
+    public FormStep FormStep
+    {
+        get => formStep;
+        set
+        {
+            SetProperty(ref formStep, value);
+            NextFormStepCommand.NotifyCanExecuteChanged();
+            PreviousFormStepCommand.NotifyCanExecuteChanged();
+
+            foreach (var step in AllFormSteps)
+            {
+                step.IsActive = step.Step == value.Step;
+            }
+        }
+    }
+
+    public ObservableCollection<FormStep> AllFormSteps { get; } = new()
+    {
+        new FormStep { Title = new LocalizedString("ONB_SELECT_PRIMARY_TOOLSET"), Step = 0 },
+        new FormStep { Title = new LocalizedString("LANGUAGE"), Step = 1 },
+        new FormStep { Title = new LocalizedString("ONB_SHORTCUTS"), Step = 2 },
+        new FormStep { Title = new LocalizedString("ONB_ANALYTICS"), Step = 3 }
+    };
+
+    public RelayCommand NextFormStepCommand { get; }
+    public RelayCommand PreviousFormStepCommand { get; }
+
+    public GeneralSettings GeneralSettings { get; } = new();
+
+    public ObservableCollection<SelectionCard<ShortcutProvider>> Templates { get; set; }
+    public ObservableCollection<SelectionCard<IToolSetHandler>> ToolSets { get; }
+
+    public AsyncRelayCommand<ShortcutProvider> SelectShortcutCommand { get; }
+
+    public RelayCommand<IToolSetHandler> SelectToolsetCommand { get; }
+
+    public OnboardingViewModel()
+    {
+        NextFormStepCommand = new RelayCommand(NextFormStep, CanNextFormStep);
+        PreviousFormStepCommand = new RelayCommand(PreviousFormStep, CanPreviousFormStep);
+
+        SelectToolsetCommand = new RelayCommand<IToolSetHandler>(x =>
+        {
+            foreach (var toolset in ToolSets)
+            {
+                toolset.IsSelected = toolset.Item == x;
+            }
+        });
+
+        SelectShortcutCommand = new AsyncRelayCommand<ShortcutProvider>(
+            async x =>
+            {
+                foreach (var template in Templates)
+                {
+                    template.IsSelected = template.Item == x;
+                }
+
+                if (x == Templates[0].Item)
+                {
+                    CommandController.Current.ResetShortcuts();
+                }
+                else
+                {
+                    await ImportShortcutTemplatePopup.ImportFromProvider(x, true);
+                }
+            });
+
+        FormStep = AllFormSteps[0];
+        GeneralSettings = new GeneralSettings();
+        Templates = new ObservableCollection<SelectionCard<ShortcutProvider>>(ShortcutProvider.GetProviders()
+            .Select(x => new SelectionCard<ShortcutProvider>(x, SelectShortcutCommand)));
+        Templates.Insert(0,
+            new SelectionCard<ShortcutProvider>(
+                new ShortcutProvider("PixiEditor")
+                {
+                    LogoPath = "/Images/PixiEditorLogo.svg", HoverLogoPath = "/Images/PixiEditorLogo.svg"
+                },
+                SelectShortcutCommand) { IsSelected = true });
+        ToolSets = new ObservableCollection<SelectionCard<IToolSetHandler>>(
+            ViewModelMain.Current.ToolsSubViewModel.AllToolSets.Select(x =>
+                new SelectionCard<IToolSetHandler>(x, SelectToolsetCommand)));
+        var firstToolSet = ToolSets.FirstOrDefault();
+        if (firstToolSet != null)
+        {
+            firstToolSet.IsSelected = true;
+        }
+    }
+
+    public void NextFormStep()
+    {
+        if (FormStep.Step == 3)
+        {
+            NextPage();
+            return;
+        }
+
+        FormStep = AllFormSteps[FormStep.Step + 1];
+    }
+
+    public void PreviousFormStep()
+    {
+        FormStep = AllFormSteps[FormStep.Step - 1];
+    }
+
+    public bool CanNextFormStep()
+    {
+        return FormStep.Step < AllFormSteps.Count;
+    }
+
+    public bool CanPreviousFormStep()
+    {
+        return FormStep.Step > 0;
+    }
+
+    public void NextPage()
+    {
+        Page++;
+    }
+
+    public void PreviousPage()
+    {
+        Page--;
+    }
+}
+
+public class FormStep : ObservableObject
+{
+    public LocalizedString Title { get; set; }
+    public int Step { get; set; }
+    private bool isActive;
+
+    public bool IsActive
+    {
+        get => isActive;
+        set => SetProperty(ref isActive, value);
+    }
+}
+
+public class SelectionCard<T> : ObservableObject
+{
+    private bool isSelected;
+
+    public bool IsSelected
+    {
+        get => isSelected;
+        set
+        {
+            SetProperty(ref isSelected, value);
+        }
+    }
+
+    public ICommand SelectCommand { get; }
+
+    public T Item { get; set; }
+
+    public SelectionCard(T item, ICommand selectCommand)
+    {
+        Item = item;
+        SelectCommand = selectCommand;
+    }
 }

+ 200 - 25
src/PixiEditor/Views/Dialogs/OnboardingDialog.axaml

@@ -1,37 +1,212 @@
-<dialogs:PixiEditorPopup xmlns="https://github.com/avaloniaui"
-                         xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
-                         xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
-                         xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
-                         xmlns:dialogs="clr-namespace:PixiEditor.Views.Dialogs"
-                         xmlns:ui="clr-namespace:PixiEditor.Extensions.UI;assembly=PixiEditor.Extensions"
-                         mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
-                         x:Class="PixiEditor.Views.Dialogs.OnboardingDialog"
-                         Title="Onboarding" Width="600" Height="345">
-    <Panel>
-        <Grid>
-            <Grid.RowDefinitions>
-                <RowDefinition Height="Auto" />
-                <RowDefinition Height="Auto" />
-                <RowDefinition Height="Auto" />
-            </Grid.RowDefinitions>
+<Window xmlns="https://github.com/avaloniaui"
+        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
+        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
+        xmlns:dialogs="clr-namespace:PixiEditor.Views.Dialogs"
+        xmlns:ui="clr-namespace:PixiEditor.Extensions.UI;assembly=PixiEditor.Extensions"
+        xmlns:userPreferences="clr-namespace:PixiEditor.ViewModels.UserPreferences"
+        xmlns:converters="clr-namespace:PixiEditor.Helpers.Converters"
+        xmlns:shortcuts="clr-namespace:PixiEditor.Views.Shortcuts"
+        mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
+        x:Class="PixiEditor.Views.Dialogs.OnboardingDialog"
+        ShowInTaskbar="False"
+        CanResize="False"
+        d:DataContext="{x:Type userPreferences:OnboardingViewModel}"
+        Title="Onboarding" Width="600" Height="345">
+    <Design.DataContext>
+        <userPreferences:OnboardingViewModel />
+    </Design.DataContext>
 
-            <TextBlock Grid.Row="0" Classes="h1"
-                       Foreground="{DynamicResource ThemeForegroundBrush}"
-                       ui:Translator.Key="ONBOARDING_TITLE" HorizontalAlignment="Center" VerticalAlignment="Center" />
-            <TextBlock Grid.Row="1"
-                       ui:Translator.Key="ONBOARDING_DESCRIPTION"
+    <Panel>
+        <Panel.Styles>
+            <Style Selector="shortcuts|ShortcutsTemplateCard.Selected">
+                <Setter Property="BorderBrush" Value="{DynamicResource ThemeAccentHighBrush}" />
+                <Setter Property="Background">
+                    <Setter.Value>
+                        <SolidColorBrush Color="{DynamicResource AccentHighColor}" Opacity="0.1" />
+                    </Setter.Value>
+                </Setter>
+            </Style>
+        </Panel.Styles>
+        <StackPanel
+            IsVisible="{Binding Page, Converter={converters:IsEqualConverter}, ConverterParameter=0}"
+            Spacing="24" Orientation="Vertical" VerticalAlignment="Center" HorizontalAlignment="Center">
+            <StackPanel Spacing="13" Orientation="Horizontal">
+                <TextBlock Classes="h1"
+                           Foreground="{DynamicResource ThemeForegroundBrush}"
+                           ui:Translator.Key="ONBOARDING_TITLE" HorizontalAlignment="Center" VerticalAlignment="Center" />
+                <Svg Path="/Images/PixiEditorLogoWithName.svg" Height="60" />
+            </StackPanel>
+            <TextBlock ui:Translator.Key="ONBOARDING_DESCRIPTION"
+                       Classes="h5"
                        Foreground="{DynamicResource ThemeForegroundBrush}"
                        HorizontalAlignment="Center" VerticalAlignment="Center" />
-            <StackPanel Orientation="Horizontal" Grid.Row="2">
+            <StackPanel HorizontalAlignment="Center"
+                        Spacing="12"
+                        Margin="0, 24, 0, 0" Orientation="Horizontal">
                 <Button ui:Translator.Key="ONBOARDING_SKIP_BTN"
-                        Command="{Binding CloseCommand}"
+                        Padding="8, 4"
+                        Command="{Binding Close, RelativeSource={RelativeSource AncestorType=dialogs:OnboardingDialog, Mode=FindAncestor}}"
                         HorizontalAlignment="Center" VerticalAlignment="Center" />
 
                 <Button Background="{DynamicResource ThemeAccentBrush}"
                         ui:Translator.Key="ONBOARDING_ACTION_BTN"
-                        Command="{Binding OpenHelloThereWindowCommand}"
+                        Command="{Binding NextPage}"
                         HorizontalAlignment="Center" VerticalAlignment="Center" />
             </StackPanel>
+        </StackPanel>
+        <Grid
+            HorizontalAlignment="Center"
+            Margin="0, 32"
+            IsVisible="{Binding Page, Converter={converters:IsEqualConverter}, ConverterParameter=1}">
+            <Grid.RowDefinitions>
+                <RowDefinition Height="Auto" />
+                <RowDefinition Height="*" />
+                <RowDefinition Height="Auto" />
+            </Grid.RowDefinitions>
+            <TextBlock Classes="h2"
+                       Foreground="{DynamicResource ThemeForegroundBrush}"
+                       ui:Translator.LocalizedString="{Binding FormStep.Title}" HorizontalAlignment="Center"
+                       VerticalAlignment="Center" />
+            <StackPanel Grid.Row="1"
+                        IsVisible="{Binding FormStep.Step, Converter={converters:IsEqualConverter}, ConverterParameter=0}"
+                        HorizontalAlignment="Center" VerticalAlignment="Center" Spacing="32" Orientation="Horizontal">
+                <ScrollViewer VerticalScrollBarVisibility="Disabled" HorizontalScrollBarVisibility="Auto">
+                    <ItemsControl
+                        ItemsSource="{Binding ToolSets}">
+                        <ItemsControl.ItemsPanel>
+                            <ItemsPanelTemplate>
+                                <StackPanel Orientation="Horizontal" />
+                            </ItemsPanelTemplate>
+                        </ItemsControl.ItemsPanel>
+                        <ItemsControl.ItemTemplate>
+                            <DataTemplate>
+                                <shortcuts:ShortcutsTemplateCard
+                                    TemplateName="{Binding Item.Name}"
+                                    Margin="0 0 5 0"
+                                    Logo="{Binding Item.LogoPath}"
+                                    Cursor="Hand"
+                                    HoverLogo="{Binding Path=Item.HoverLogoPath}" />
+                            </DataTemplate>
+                        </ItemsControl.ItemTemplate>
+                    </ItemsControl>
+                </ScrollViewer>
+            </StackPanel>
+            <StackPanel Grid.Row="1"
+                        HorizontalAlignment="Center" VerticalAlignment="Center"
+                        Spacing="32"
+                        IsVisible="{Binding FormStep.Step, Converter={converters:IsEqualConverter}, ConverterParameter=1}">
+                <ComboBox Width="200" HorizontalAlignment="Center" Margin="0, -20, 0, 0"
+                          ItemsSource="{Binding GeneralSettings.AvailableLanguages}"
+                          SelectedItem="{Binding GeneralSettings.SelectedLanguage, Mode=TwoWay}">
+                    <ComboBox.ItemTemplate>
+                        <DataTemplate>
+                            <StackPanel Orientation="Horizontal" Height="20">
+                                <Image
+                                    MaxHeight="20"
+                                    Margin="3, 0"
+                                    VerticalAlignment="Center"
+                                    Source="{Binding IconFullPath, Converter={converters:ImagePathToBitmapConverter}}" />
+                                <TextBlock VerticalAlignment="Center" Text="{Binding Name}" />
+                            </StackPanel>
+                        </DataTemplate>
+                    </ComboBox.ItemTemplate>
+                </ComboBox>
+                <TextBlock DockPanel.Dock="Bottom" Classes="subtext" ui:Translator.Key="LANGUAGE_INFO" />
+            </StackPanel>
+            <StackPanel Grid.Row="1"
+                        IsVisible="{Binding FormStep.Step, Converter={converters:IsEqualConverter}, ConverterParameter=2}"
+                        HorizontalAlignment="Center" VerticalAlignment="Center" Spacing="32" Orientation="Vertical">
+                <ScrollViewer VerticalScrollBarVisibility="Disabled" HorizontalScrollBarVisibility="Auto">
+                    <ItemsControl
+                        ItemsSource="{Binding Templates}">
+                        <ItemsControl.ItemsPanel>
+                            <ItemsPanelTemplate>
+                                <StackPanel Orientation="Horizontal" />
+                            </ItemsPanelTemplate>
+                        </ItemsControl.ItemsPanel>
+                        <ItemsControl.ItemTemplate>
+                            <DataTemplate>
+                                <shortcuts:ShortcutsTemplateCard
+                                    Width="150"
+                                    Height="150"
+                                    TemplateName="{Binding Item.Name}"
+                                    Margin="0 0 5 0"
+                                    Logo="{Binding Item.LogoPath}"
+                                    Classes.Selected="{Binding IsSelected}"
+                                    Cursor="Hand"
+                                    Name="ShortcutsTemplateCard"
+                                    PressedCommand="{Binding SelectCommand}"
+                                    PressedCommandParameter="{Binding Item}"
+                                    HoverLogo="{Binding Path=Item.HoverLogoPath}">
+                                </shortcuts:ShortcutsTemplateCard>
+                            </DataTemplate>
+                        </ItemsControl.ItemTemplate>
+                    </ItemsControl>
+                </ScrollViewer>
+            </StackPanel>
+            <StackPanel Grid.Row="1"
+                        IsVisible="{Binding FormStep.Step, Converter={converters:IsEqualConverter}, ConverterParameter=3}"
+                        VerticalAlignment="Center" Orientation="Vertical">
+                <CheckBox HorizontalAlignment="Center"
+                          IsChecked="{Binding GeneralSettings.AnalyticsEnabled, Mode=TwoWay}"
+                          ui:Translator.Key="ENABLE_ANALYTICS" d:Content="Enable Analytics" />
+                <TextBlock Padding="20" Classes="subtext" ui:Translator.Key="ANALYTICS_INFO_DETAILED"
+                           TextWrapping="Wrap">
+                    <TextBlock Text=" " />
+                    <TextBlock ui:Hyperlink.Url="https://pixieditor.net/docs/privacy-policy"
+                               ui:Translator.Key="PRIVACY_POLICY" />
+                </TextBlock>
+            </StackPanel>
         </Grid>
+        <DockPanel
+            LastChildFill="True"
+            VerticalAlignment="Bottom"
+            HorizontalAlignment="Stretch"
+            IsVisible="{Binding Page, Converter={converters:IsEqualConverter}, ConverterParameter=1}"
+            Margin="20, 0, 20, 32">
+            <Button ui:Translator.Key="ONB_BACK_BTN" DockPanel.Dock="Left"
+                    Width="100"
+                    Padding="8, 4"
+                    Command="{Binding PreviousFormStepCommand}"
+                    HorizontalAlignment="Left" VerticalAlignment="Bottom" />
+            <Button ui:Translator.Key="ONB_NEXT_BTN"
+                    DockPanel.Dock="Right"
+                    Width="100"
+                    Padding="8, 4"
+                    Background="{DynamicResource ThemeAccentBrush}"
+                    Command="{Binding NextFormStepCommand}"
+                    HorizontalAlignment="Right" VerticalAlignment="Bottom" />
+
+            <ItemsControl HorizontalAlignment="Center" ItemsSource="{Binding AllFormSteps}">
+                <ItemsControl.Styles>
+                    <Style Selector="Rectangle.Active">
+                        <Setter Property="Fill" Value="{DynamicResource ThemeAccentBrush}" />
+                    </Style>
+                </ItemsControl.Styles>
+                <ItemsControl.ItemsPanel>
+                    <ItemsPanelTemplate>
+                        <StackPanel Orientation="Horizontal" Spacing="15" />
+                    </ItemsPanelTemplate>
+                </ItemsControl.ItemsPanel>
+                <ItemsControl.ItemTemplate>
+                    <DataTemplate DataType="userPreferences:FormStep">
+                        <Rectangle Width="50"
+                                   Height="5"
+                                   RadiusX="2.5"
+                                   RadiusY="2.5"
+                                   Classes.Active="{Binding IsActive}"
+                                   Fill="{DynamicResource ThemeControlHighlightBrush}">
+                            <Rectangle.Transitions>
+                                <Transitions>
+                                    <BrushTransition Property="Fill" Duration="0.2" />
+                                </Transitions>
+                            </Rectangle.Transitions>
+                        </Rectangle>
+                    </DataTemplate>
+                </ItemsControl.ItemTemplate>
+            </ItemsControl>
+
+        </DockPanel>
     </Panel>
-</dialogs:PixiEditorPopup>
+</Window>

+ 1 - 0
src/PixiEditor/Views/Dialogs/OnboardingDialog.axaml.cs

@@ -1,4 +1,5 @@
 using Avalonia.Controls;
+using Avalonia.Input;
 
 namespace PixiEditor.Views.Dialogs;
 

+ 9 - 0
src/PixiEditor/Views/Dialogs/PixiEditorPopup.cs

@@ -33,6 +33,15 @@ public partial class PixiEditorPopup : Window, IPopupWindow
         AvaloniaProperty.Register<PixiEditorPopup, ICommand>(
             nameof(CloseCommand));
 
+    public static readonly StyledProperty<bool> ShowTitleBarProperty = AvaloniaProperty.Register<PixiEditorPopup, bool>(
+        nameof(ShowTitleBar), defaultValue: true);
+
+    public bool ShowTitleBar
+    {
+        get => GetValue(ShowTitleBarProperty);
+        set => SetValue(ShowTitleBarProperty, value);
+    }
+
     public ICommand CloseCommand
     {
         get => GetValue(CloseCommandProperty);

+ 38 - 14
src/PixiEditor/Views/Shortcuts/ImportShortcutTemplatePopup.axaml.cs

@@ -23,6 +23,11 @@ internal partial class ImportShortcutTemplatePopup : PixiEditorPopup
 
     [Command.Internal("PixiEditor.Shortcuts.Provider.ImportDefault")]
     public static void ImportDefaults(ShortcutProvider provider)
+    {
+        ImportDefaults(provider, false);
+    }
+
+    public static void ImportDefaults(ShortcutProvider provider, bool quiet)
     {
         if (provider is not IShortcutDefaults defaults)
         {
@@ -32,11 +37,17 @@ internal partial class ImportShortcutTemplatePopup : PixiEditorPopup
         CommandController.Current.ResetShortcuts();
         CommandController.Current.Import(defaults.DefaultShortcuts);
 
-        Success(provider);
+        if (!quiet)
+            Success(provider);
     }
 
     [Command.Internal("PixiEditor.Shortcuts.Provider.ImportInstallation")]
     public static void ImportInstallation(ShortcutProvider provider)
+    {
+        ImportInstallation(provider, false);
+    }
+
+    public static void ImportInstallation(ShortcutProvider provider, bool quiet)
     {
         if (provider is not IShortcutInstallation defaults)
         {
@@ -55,10 +66,14 @@ internal partial class ImportShortcutTemplatePopup : PixiEditorPopup
             return;
         }
 
-        Success(provider);
+        if (!quiet)
+        {
+            Success(provider);
+        }
     }
 
-    private static void Success(ShortcutProvider provider) => NoticeDialog.Show(new LocalizedString("SHORTCUTS_IMPORTED", provider.Name), "SUCCESS");
+    private static void Success(ShortcutProvider provider) =>
+        NoticeDialog.Show(new LocalizedString("SHORTCUTS_IMPORTED", provider.Name), "SUCCESS");
 
     // TODO figure out what these are for
     /*
@@ -78,27 +93,36 @@ internal partial class ImportShortcutTemplatePopup : PixiEditorPopup
     /// </summary>
     /// <param name="provider">Shortcut provider.</param>
     /// <returns>True if imported shortcuts.</returns>
-    private async Task<bool> ImportFromProvider(ShortcutProvider? provider)
+    public static async Task<bool> ImportFromProvider(ShortcutProvider? provider, bool quiet = false)
     {
         if (provider is null)
             return false;
-        if (provider.ProvidesFromInstallation && provider.HasInstallationPresent)
+        if (provider is { ProvidesFromInstallation: true, HasInstallationPresent: true })
         {
-            OptionsDialog<string> dialog = new(provider.Name, new LocalizedString("SHORTCUT_PROVIDER_DETECTED"), MainWindow.Current!)
+            if (!quiet)
             {
-                { new LocalizedString("IMPORT_INSTALLATION_OPTION1"), x => ImportInstallation(provider) },
-                { new LocalizedString("IMPORT_INSTALLATION_OPTION2"), x => ImportDefaults(provider) },
-            };
-
-            return await dialog.ShowDialog();
+                OptionsDialog<string> dialog =
+                    new(provider.Name, new LocalizedString("SHORTCUT_PROVIDER_DETECTED", provider.Name),
+                        MainWindow.Current!)
+                    {
+                        {
+                            new LocalizedString("IMPORT_INSTALLATION_OPTION1"), x => ImportInstallation(provider, quiet)
+                        },
+                        { new LocalizedString("IMPORT_INSTALLATION_OPTION2"), x => ImportDefaults(provider, quiet) },
+                    };
+                return await dialog.ShowDialog();
+            }
+
+            ImportInstallation(provider, quiet);
+            return true;
         }
-        
+
         if (provider.HasDefaultShortcuts)
         {
-            ImportDefaults(provider);
+            ImportDefaults(provider, quiet);
             return true;
         }
-        
+
         return false;
     }
 

+ 0 - 42
src/PixiEditor/Views/Shortcuts/ShortcutsTemplateCard.axaml

@@ -1,42 +0,0 @@
-<UserControl
-    xmlns="https://github.com/avaloniaui"
-    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
-    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
-    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
-    xmlns:converters="clr-namespace:PixiEditor.Helpers.Converters"
-    mc:Ignorable="d"
-    d:DesignWidth="800"
-    d:DesignHeight="450" Name="card"
-    x:Class="PixiEditor.Views.Shortcuts.ShortcutsTemplateCard">
-    <UserControl.Styles>
-        <Style Selector="Border Image">
-            <Setter Property="Width" Value="72"/>
-            <Setter Property="Height" Value="72"/>
-            <Setter Property="Transitions">
-                <Transitions>
-                    <DoubleTransition Duration="0:0:0.15" Property="Width"/>
-                    <DoubleTransition Duration="0:0:0.15" Property="Height"/>
-                </Transitions>
-            </Setter>
-        </Style>
-        <Style Selector="Border:pointerover Image">
-            <Setter Property="Width" Value="100"/>
-            <Setter Property="Height" Value="100"/>
-        </Style>
-    </UserControl.Styles>
-    <Border BorderThickness="1" Height="150" Width="150" Background="{DynamicResource ThemeBackgroundBrush1}"
-            CornerRadius="15" PointerEntered="OnBorderPointerEntered" PointerExited="OnBorderPointerExited">
-        <Grid>
-            <Grid.RowDefinitions>
-                <RowDefinition Height="*"/>
-                <RowDefinition Height="30"/>
-            </Grid.RowDefinitions>
-            <Image Grid.Row="0" Grid.RowSpan="2" Name="img" HorizontalAlignment="Center" VerticalAlignment="Center"
-                   Source="{Binding ElementName=card, Path=Logo, Converter={converters:ImagePathToBitmapConverter}}"/>
-            <Label 
-                Grid.Row="1" HorizontalAlignment="Center" FontWeight="Bold" Margin="0" Padding="0"
-                Content="{Binding ElementName=card, Path=TemplateName}"/>
-                <!--Style="{StaticResource BaseLabel}"-->
-        </Grid>
-    </Border>
-</UserControl>

+ 0 - 62
src/PixiEditor/Views/Shortcuts/ShortcutsTemplateCard.axaml.cs

@@ -1,62 +0,0 @@
-using Avalonia;
-using Avalonia.Controls;
-using Avalonia.Input;
-using PixiEditor.Helpers.Converters;
-
-namespace PixiEditor.Views.Shortcuts;
-
-public partial class ShortcutsTemplateCard : UserControl
-{
-    public static readonly StyledProperty<string> TemplateNameProperty = 
-        AvaloniaProperty.Register<ShortcutsTemplateCard, string>(nameof(TemplateName));
-
-    public string TemplateName
-    {
-        get { return (string)GetValue(TemplateNameProperty); }
-        set { SetValue(TemplateNameProperty, value); }
-    }
-
-    public static readonly StyledProperty<string> LogoProperty =
-        AvaloniaProperty.Register<ShortcutsTemplateCard, string>(nameof(Logo));
-
-    public static readonly StyledProperty<string> HoverLogoProperty =
-        AvaloniaProperty.Register<ShortcutsTemplateCard, string>(nameof(HoverLogo));
-
-    public string HoverLogo
-    {
-        get { return (string)GetValue(HoverLogoProperty); }
-        set { SetValue(HoverLogoProperty, value); }
-    }
-    
-    public string Logo
-    {
-        get { return (string)GetValue(LogoProperty); }
-        set { SetValue(LogoProperty, value); }
-    }
-    
-    public ShortcutsTemplateCard()
-    {
-        InitializeComponent();
-    }
-
-    private void OnBorderPointerExited(object? sender, PointerEventArgs e)
-    {
-        if (string.IsNullOrEmpty(HoverLogo))
-        {
-            return;
-        }
-        
-        img.Source = ImagePathToBitmapConverter.LoadBitmapFromRelativePath(Logo);
-    }
-
-    private void OnBorderPointerEntered(object? sender, PointerEventArgs e)
-    {
-        if (string.IsNullOrEmpty(HoverLogo))
-        {
-            return;
-        }
-
-        img.Source = ImagePathToBitmapConverter.LoadBitmapFromRelativePath(HoverLogo);
-    }
-}
-

+ 90 - 0
src/PixiEditor/Views/Shortcuts/ShortcutsTemplateCard.cs

@@ -0,0 +1,90 @@
+using System.Windows.Input;
+using Avalonia;
+using Avalonia.Controls;
+using Avalonia.Controls.Primitives;
+using Avalonia.Input;
+using PixiEditor.Helpers.Converters;
+
+namespace PixiEditor.Views.Shortcuts;
+
+public partial class ShortcutsTemplateCard : TemplatedControl
+{
+    public static readonly StyledProperty<string> TemplateNameProperty =
+        AvaloniaProperty.Register<ShortcutsTemplateCard, string>(nameof(TemplateName));
+
+    public string TemplateName
+    {
+        get { return (string)GetValue(TemplateNameProperty); }
+        set { SetValue(TemplateNameProperty, value); }
+    }
+
+    public static readonly StyledProperty<string> LogoProperty =
+        AvaloniaProperty.Register<ShortcutsTemplateCard, string>(nameof(Logo));
+
+    public static readonly StyledProperty<string> HoverLogoProperty =
+        AvaloniaProperty.Register<ShortcutsTemplateCard, string>(nameof(HoverLogo));
+
+    public static readonly StyledProperty<ICommand> PressedCommandProperty = AvaloniaProperty.Register<ShortcutsTemplateCard, ICommand>(
+        nameof(PressedCommand));
+
+    public static readonly StyledProperty<object> PressedCommandParameterProperty = AvaloniaProperty.Register<ShortcutsTemplateCard, object>(
+        nameof(PressedCommandParameter));
+
+    public object PressedCommandParameter
+    {
+        get => GetValue(PressedCommandParameterProperty);
+        set => SetValue(PressedCommandParameterProperty, value);
+    }
+
+    public ICommand PressedCommand
+    {
+        get => GetValue(PressedCommandProperty);
+        set => SetValue(PressedCommandProperty, value);
+    }
+
+    public string HoverLogo
+    {
+        get { return (string)GetValue(HoverLogoProperty); }
+        set { SetValue(HoverLogoProperty, value); }
+    }
+
+    public string Logo
+    {
+        get { return (string)GetValue(LogoProperty); }
+        set { SetValue(LogoProperty, value); }
+    }
+
+    static ShortcutsTemplateCard()
+    {
+        LogoProperty.Changed.Subscribe(OnLogoChanged);
+    }
+
+    protected override void OnPointerPressed(PointerPressedEventArgs e)
+    {
+        base.OnPointerPressed(e);
+        if (e.GetCurrentPoint(this).Properties.IsLeftButtonPressed)
+        {
+            if (PressedCommand == null)
+            {
+                return;
+            }
+
+            if (PressedCommand.CanExecute(PressedCommandParameter))
+            {
+                PressedCommand?.Execute(PressedCommandParameter);
+            }
+        }
+    }
+
+    private static void OnLogoChanged(AvaloniaPropertyChangedEventArgs e)
+    {
+        if (e.Sender is ShortcutsTemplateCard card)
+        {
+            card.Logo = (string)e.NewValue;
+            if (string.IsNullOrEmpty(card.HoverLogo))
+            {
+                card.HoverLogo = (string)e.NewValue;
+            }
+        }
+    }
+}

+ 0 - 5
src/PixiEditor/Views/Windows/Settings/SettingsWindow.axaml

@@ -57,11 +57,6 @@
                         <Style Selector=":is(Control).leftOffset">
                             <Setter Property="Margin" Value="20, 0, 0, 0" />
                         </Style>
-                        <Style Selector=":is(TextBlock).subtext">
-                            <Setter Property="FontSize" Value="12" />
-                            <Setter Property="Foreground" Value="{DynamicResource ThemeForegroundLowBrush}" />
-                            <Setter Property="TextWrapping" Value="Wrap" />
-                        </Style>
                     </Grid.Styles>
                     <ScrollViewer>
                         <ScrollViewer.IsVisible>