Bladeren bron

Added extensions window

CPKreuz 4 jaren geleden
bovenliggende
commit
3d5364e5a4

+ 15 - 0
PixiEditor.ExtensionExample/ExtensionPage.xaml

@@ -0,0 +1,15 @@
+<UserControl x:Class="PixiEditor.ExtensionExample.ExtensionPage"
+             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
+             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
+             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
+             xmlns:local="clr-namespace:PixiEditor.ExtensionExample"
+             mc:Ignorable="d" 
+             d:DesignHeight="450" d:DesignWidth="800">
+    <StackPanel>
+        <TextBlock>This is a example extension, showing off how to make your own extension.</TextBlock>
+        <Grid Height="20"/>
+        <TextBlock>This in particular is the extension page.</TextBlock>
+        <TextBlock>You can make your own extension page by creating a new user control, and setting it as the extension page in your extension</TextBlock>
+    </StackPanel>
+</UserControl>

+ 15 - 0
PixiEditor.ExtensionExample/ExtensionPage.xaml.cs

@@ -0,0 +1,15 @@
+using System.Windows.Controls;
+
+namespace PixiEditor.ExtensionExample
+{
+    /// <summary>
+    /// Interaction logic for ExtensionPage.xaml
+    /// </summary>
+    public partial class ExtensionPage : UserControl
+    {
+        public ExtensionPage()
+        {
+            InitializeComponent();
+        }
+    }
+}

+ 9 - 0
PixiEditor.ExtensionExample/PixiEditor.ExtensionExample.csproj

@@ -2,6 +2,7 @@
 
   <PropertyGroup>
     <TargetFramework>net5.0-windows7</TargetFramework>
+    <UseWPF>true</UseWPF>
   </PropertyGroup>
 
   <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
@@ -13,8 +14,16 @@
     <Exec Command="echo F|xcopy $(TargetPath) %25LocalAppData%25\PixiEditor\Extensions\$(TargetName)\$(TargetFileName) /y" />
   </Target>
 
+  <ItemGroup>
+    <None Remove="PixiExampleLogo.png" />
+  </ItemGroup>
+
   <ItemGroup>
     <ProjectReference Include="..\PixiEditor.SDK\PixiEditor.SDK.csproj" />
   </ItemGroup>
 
+  <ItemGroup>
+    <Resource Include="PixiExampleLogo.png" />
+  </ItemGroup>
+
 </Project>

BIN
PixiEditor.ExtensionExample/PixiExampleLogo.png


+ 16 - 2
PixiEditor.ExtensionExample/SomeExtension.cs

@@ -1,6 +1,11 @@
 using PixiEditor.ExtensionExample;
 using PixiEditor.SDK;
 using System;
+using System.IO;
+using System.Reflection;
+using System.Windows;
+using System.Windows.Media;
+using System.Windows.Media.Imaging;
 
 [assembly: PixiEditorExtension(typeof(SomeExtension))]
 
@@ -10,14 +15,23 @@ namespace PixiEditor.ExtensionExample
     {
         public override string Name { get; } = "PixiEditor.ExampleExtension";
 
-        public override string DisplayName { get; } = "Example extension";
+        public override string DisplayName { get; } = "Example Extension";
 
         public override string Description { get; } = "A exmaple extension showing how extensions work";
 
-        public override Version Version { get; } = new Version(1, 0, 0, 0);
+        public override FrameworkElement ExtensionPage { get; } = new ExtensionPage();
+
+        public override Version Version { get; } = new Version(4, 2, 0, 69);
+
+        public override ImageSource Icon { get; }
 
         public override bool IsVersionSupported(Version pixiEditorVersion) => true;
 
+        public SomeExtension()
+        {
+            Icon = LoadImageFromResource("./PixiExampleLogo.png");
+        }
+
         public override void Load(ExtensionLoadingInformation information)
         {
             if (Preferences.GetLocalPreference("Test", true))

+ 14 - 0
PixiEditor.SDK/Extension.cs

@@ -1,6 +1,10 @@
 using System;
 using System.Collections.Generic;
 using System.Diagnostics;
+using System.IO;
+using System.Reflection;
+using System.Windows;
+using System.Windows.Media;
 
 namespace PixiEditor.SDK
 {
@@ -13,6 +17,10 @@ namespace PixiEditor.SDK
 
         public abstract string Description { get; }
 
+        public abstract FrameworkElement ExtensionPage { get; }
+
+        public abstract ImageSource Icon { get; }
+
         public string ExtensionPath { get; internal set; }
 
         public abstract Version Version { get; }
@@ -26,5 +34,11 @@ namespace PixiEditor.SDK
         public abstract bool IsVersionSupported(Version pixiEditorVersion);
 
         public abstract void Load(ExtensionLoadingInformation information);
+
+        protected static ImageSource LoadImageFromResource(string path)
+        {
+            var assemblyName = Assembly.GetCallingAssembly().GetName();
+            return new ImageSourceConverter().ConvertFromString(Path.Join($"pack://application:,,,/{assemblyName.Name};component/", path)) as ImageSource;
+        }
     }
 }

+ 33 - 0
PixiEditor/Helpers/Converters/AlternativeFontStyleConverter.cs

@@ -0,0 +1,33 @@
+using System;
+using System.Globalization;
+using System.Windows;
+using System.Windows.Data;
+
+namespace PixiEditor.Helpers.Converters
+{
+    public class AlternativeFontStyleConverter : IValueConverter
+    {
+        public bool OnlyEmptyString { get; set; }
+
+        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
+        {
+            if (OnlyEmptyString && string.IsNullOrEmpty((string)value))
+            {
+                return FontStyles.Italic;
+            }
+            else if (string.IsNullOrWhiteSpace((string)value))
+            {
+                return FontStyles.Italic;
+            }
+            else
+            {
+                return FontStyles.Normal;
+            }
+        }
+
+        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
+        {
+            throw new NotImplementedException();
+        }
+    }
+}

+ 36 - 0
PixiEditor/Helpers/Converters/AlternativeTextConverter.cs

@@ -0,0 +1,36 @@
+using System;
+using System.Collections.Generic;
+using System.Globalization;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using System.Windows.Data;
+
+namespace PixiEditor.Helpers.Converters
+{
+    public class AlternativeTextConverter : IValueConverter
+    {
+        public bool OnlyEmptyString { get; set; }
+
+        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
+        {
+            if (OnlyEmptyString && string.IsNullOrEmpty((string)value))
+            {
+                return parameter;
+            }
+            else if (string.IsNullOrWhiteSpace((string)value))
+            {
+                return parameter;
+            }
+            else
+            {
+                return value;
+            }
+        }
+
+        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
+        {
+            throw new NotImplementedException();
+        }
+    }
+}

+ 24 - 1
PixiEditor/Models/BaseExtension.cs → PixiEditor/Models/Extensions/BaseExtension.cs

@@ -2,15 +2,21 @@
 using PixiEditor.SDK;
 using System;
 using System.Collections.Generic;
+using System.IO;
 using System.Linq;
+using System.Reflection;
 using System.Text;
 using System.Threading.Tasks;
+using System.Windows;
+using System.Windows.Controls;
+using System.Windows.Media;
+using System.Windows.Media.Imaging;
 
 namespace PixiEditor.Models
 {
     class BaseExtension : Extension
     {
-        public override string Name => "PixiEditor.PixiEditorBase";
+        public override string Name => "PixiEditor.PixiEditor";
 
         public override string DisplayName => "PixiEditor";
 
@@ -18,8 +24,18 @@ namespace PixiEditor.Models
 
         public override Version Version => new Version(1, 0, 0, 0);
 
+        public override FrameworkElement ExtensionPage { get; }
+
+        public override ImageSource Icon { get; }
+
         public override bool IsVersionSupported(Version pixiEditorVersion) => true;
 
+        public BaseExtension()
+        {
+            ExtensionPage = CreatePage();
+            Icon = LoadImageFromResource("Images/PixiEditorLogo.png");
+        }
+
         public override void Load(ExtensionLoadingInformation information)
         {
             information
@@ -27,5 +43,12 @@ namespace PixiEditor.Models
                 .AddImageParser<PngParser>()
                 .AddImageParser<JpegParser>();
         }
+
+        private FrameworkElement CreatePage()
+        {
+            TextBlock block = new TextBlock();
+            block.Text = Description;
+            return block;
+        }
     }
 }

+ 55 - 0
PixiEditor/Models/Extensions/XAMLSDKExtension.cs

@@ -0,0 +1,55 @@
+using PixiEditor.SDK;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using System.Windows;
+using System.Windows.Controls;
+using System.Windows.Media;
+
+namespace PixiEditor.Models.Extensions
+{
+    public class XAMLSDKExtension : Extension
+    {
+        public override string Name { get => XAMLName; }
+
+        public string XAMLName { get; set; }
+
+        public override string DisplayName { get => XAMLDisplayName; }
+
+        public string XAMLDisplayName { get; set; }
+
+        public override string Description { get => XAMLDescription; }
+
+        public string XAMLDescription { get; set; }
+
+        public override Version Version => throw new NotImplementedException();
+
+        public override FrameworkElement ExtensionPage { get; }
+
+        public override ImageSource Icon => IconSource;
+
+        public ImageSource IconSource { get; set; }
+
+        public XAMLSDKExtension()
+        {
+            TextBlock textBlock = new TextBlock
+            {
+                Text = Description
+            };
+
+            ExtensionPage = textBlock;
+        }
+
+        public override bool IsVersionSupported(Version pixiEditorVersion)
+        {
+            throw new NotImplementedException();
+        }
+
+        public override void Load(ExtensionLoadingInformation information)
+        {
+            throw new NotImplementedException();
+        }
+    }
+}

+ 29 - 0
PixiEditor/ViewModels/ExtensionWindowViewModel.cs

@@ -0,0 +1,29 @@
+using PixiEditor.Helpers;
+using PixiEditor.SDK;
+using PixiEditor.ViewModels.SubViewModels.Main;
+
+namespace PixiEditor.ViewModels
+{
+    public class ExtensionWindowViewModel : ViewModelBase
+    {
+        private Extension selectedExtension;
+
+        public ExtensionViewModel MainWindowExtensionsViewModel { get; }
+
+        public Extension SelectedExtension { get => selectedExtension; set => SetProperty(ref selectedExtension, value); }
+
+        public RelayCommand SelectExtensionCommand { get; }
+
+        public ExtensionWindowViewModel(ExtensionViewModel extensionViewModel)
+        {
+            MainWindowExtensionsViewModel = extensionViewModel;
+            SelectedExtension = MainWindowExtensionsViewModel.Extensions[0];
+            SelectExtensionCommand = new RelayCommand(SelectExtension);
+        }
+
+        private void SelectExtension(object param)
+        {
+            SelectedExtension = (Extension)param;
+        }
+    }
+}

+ 17 - 1
PixiEditor/ViewModels/SubViewModels/Main/ExtensionViewModel.cs

@@ -1,8 +1,11 @@
-using PixiEditor.Models;
+using PixiEditor.Helpers;
+using PixiEditor.Models;
 using PixiEditor.Models.UserPreferences;
 using PixiEditor.SDK;
+using PixiEditor.Views.Dialogs;
 using System;
 using System.Collections.Generic;
+using System.Collections.ObjectModel;
 using System.IO;
 
 namespace PixiEditor.ViewModels.SubViewModels.Main
@@ -11,6 +14,10 @@ namespace PixiEditor.ViewModels.SubViewModels.Main
     {
         internal SDKManager SDKManager { get; set; }
 
+        public RelayCommand OpenExtensionsWindowCommand { get; set; }
+
+        public ReadOnlyCollection<Extension> Extensions => new ReadOnlyCollection<Extension>(SDKManager.Extensions);
+
         public ExtensionViewModel(ViewModelMain owner)
             : base(owner)
         {
@@ -24,6 +31,15 @@ namespace PixiEditor.ViewModels.SubViewModels.Main
             SDKManager.LoadExtensions(Path.Join(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "PixiEditor", "Extensions"));
             SDKManager.SetupExtensions();
 
+            OpenExtensionsWindowCommand = new RelayCommand(OpenExtensionsWindow);
+        }
+
+        public void OpenExtensionsWindow() => OpenExtensionsWindow(null);
+
+        private void OpenExtensionsWindow(object param)
+        {
+            ExtensionWindow window = new ExtensionWindow(this);
+            window.Show();
         }
 
         private void SavePreference(Extension ext, string name, object obj, PreferenceStorageLocation location)

+ 71 - 0
PixiEditor/Views/Dialogs/ExtensionWindow.xaml

@@ -0,0 +1,71 @@
+<Window x:Class="PixiEditor.Views.Dialogs.ExtensionWindow"
+        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
+        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:local="clr-namespace:PixiEditor.Views.Dialogs" xmlns:uc="clr-namespace:PixiEditor.Views.UserControls"
+        xmlns:behaviours="clr-namespace:PixiEditor.Helpers.Behaviours" xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
+        xmlns:converters="clr-namespace:PixiEditor.Helpers.Converters" xmlns:viewModels="clr-namespace:PixiEditor.ViewModels" 
+        xmlns:sdk="clr-namespace:PixiEditor.SDK;assembly=PixiEditor.SDK" xmlns:extensions="clr-namespace:PixiEditor.Models.Extensions"
+        d:DataContext="{d:DesignInstance Type=viewModels:ExtensionWindowViewModel}"
+        mc:Ignorable="d" Topmost="True"
+        Title="Extensions"
+        Height="450" Width="800"
+        MinHeight="180" MinWidth="600">
+    <WindowChrome.WindowChrome>
+        <WindowChrome CaptionHeight="32"
+                      ResizeBorderThickness="{x:Static SystemParameters.WindowResizeBorderThickness}" />
+    </WindowChrome.WindowChrome>
+
+    <!--<Window.CommandBindings>
+        <CommandBinding Command="{x:Static SystemCommands.CloseWindowCommand}" CanExecute="CommandBinding_CanExecute"
+                        Executed="CommandBinding_Executed_Close" />
+    </Window.CommandBindings>-->
+
+    <Grid Background="{StaticResource MainColor}">
+        <Grid.ColumnDefinitions>
+            <ColumnDefinition Width="250"/>
+            <ColumnDefinition Width="*"/>
+        </Grid.ColumnDefinitions>
+        <Grid.RowDefinitions>
+            <RowDefinition Height="35" />
+            <RowDefinition />
+        </Grid.RowDefinitions>
+        <i:Interaction.Behaviors>
+            <behaviours:ClearFocusOnClickBehavior/>
+        </i:Interaction.Behaviors>
+
+        <DockPanel Grid.Column="0" Grid.Row="0" Grid.ColumnSpan="2" Background="{StaticResource MainColor}">
+            <TextBlock Foreground="White" VerticalAlignment="Center" Margin="10,0,0,0" FontSize="16">Extensions</TextBlock>
+            <Button DockPanel.Dock="Right" HorizontalAlignment="Right" Style="{StaticResource CloseButtonStyle}"
+                    WindowChrome.IsHitTestVisibleInChrome="True" ToolTip="Close"
+                    Command="{x:Static SystemCommands.CloseWindowCommand}" />
+        </DockPanel>
+        <ScrollViewer Grid.Row="1" VerticalScrollBarVisibility="Auto">
+            <ItemsControl ItemsSource="{Binding MainWindowExtensionsViewModel.Extensions}">
+                <d:ItemsControl.ItemsSource>
+                    <x:Array Type="{x:Type sdk:Extension}">
+                        <extensions:XAMLSDKExtension XAMLDisplayName="Example Extension Name" XAMLDescription="This is the desciprtion of the example extension. It should automatically wrap"/>
+                        <extensions:XAMLSDKExtension XAMLDisplayName="Extension with no description" XAMLDescription=" "/>
+                        <extensions:XAMLSDKExtension XAMLDisplayName=" " XAMLDescription="Description of a extension with no name"/>
+                    </x:Array>
+                </d:ItemsControl.ItemsSource>
+                <ItemsControl.ItemTemplate>
+                    <DataTemplate DataType="{x:Type sdk:Extension}">
+                        <uc:ExtensionItem MinHeight="60" MaxHeight="120"
+                            SelectedExtension="{Binding DataContext.SelectedExtension, RelativeSource={RelativeSource AncestorType=ItemsControl, Mode=FindAncestor}}" Extension="{Binding}"/>
+                    </DataTemplate>
+                </ItemsControl.ItemTemplate>
+                <ItemsControl.ItemsPanel>
+                    <ItemsPanelTemplate>
+                        <StackPanel/>
+                    </ItemsPanelTemplate>
+                </ItemsControl.ItemsPanel>
+            </ItemsControl>
+        </ScrollViewer>
+        <ScrollViewer Grid.Row="1" Grid.Column="1" Background="{StaticResource AccentColor}"
+                      VerticalScrollBarVisibility="Auto">
+            <uc:ExtensionPage DataContext="{Binding SelectedExtension}"/>
+        </ScrollViewer>
+    </Grid>
+</Window>

+ 30 - 0
PixiEditor/Views/Dialogs/ExtensionWindow.xaml.cs

@@ -0,0 +1,30 @@
+using PixiEditor.ViewModels;
+using PixiEditor.ViewModels.SubViewModels.Main;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using System.Windows;
+using System.Windows.Controls;
+using System.Windows.Data;
+using System.Windows.Documents;
+using System.Windows.Input;
+using System.Windows.Media;
+using System.Windows.Media.Imaging;
+using System.Windows.Shapes;
+
+namespace PixiEditor.Views.Dialogs
+{
+    /// <summary>
+    /// Interaction logic for ExtensionWindow.xaml
+    /// </summary>
+    public partial class ExtensionWindow : Window
+    {
+        public ExtensionWindow(ExtensionViewModel viewModel)
+        {
+            DataContext = new ExtensionWindowViewModel(viewModel);
+            InitializeComponent();
+        }
+    }
+}

+ 3 - 0
PixiEditor/Views/MainWindow.xaml

@@ -153,6 +153,9 @@
                     <MenuItem Header="_Third Party Licenses" Command="{Binding MiscSubViewModel.OpenHyperlinkCommand}"
                               CommandParameter="https://github.com/PixiEditor/PixiEditor/wiki/Third-party-licenses"/>
                 </MenuItem>
+                <MenuItem Header="_Extensions">
+                    <MenuItem Header="_Manage Extensions" Command="{Binding ExtensionSubViewModel.OpenExtensionsWindowCommand}"/>
+                </MenuItem>
                 <MenuItem Header="_Debug" Visibility="{Binding IsDebug, Converter={StaticResource BoolToVisibilityConverter}}">
                     <MenuItem Header="_Open Local App Data" Command="{Binding DebugSubViewModel.OpenFolderCommand}"
                               CommandParameter="%LocalAppData%/PixiEditor"/>

+ 64 - 0
PixiEditor/Views/UserControls/ExtensionItem.xaml

@@ -0,0 +1,64 @@
+<UserControl x:Class="PixiEditor.Views.UserControls.ExtensionItem"
+             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
+             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
+             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
+             xmlns:converters="clr-namespace:PixiEditor.Helpers.Converters"
+             xmlns:local="clr-namespace:PixiEditor.Views.UserControls"
+             xmlns:extensions="clr-namespace:PixiEditor.Models.Extensions"
+             mc:Ignorable="d" x:Name="uc"
+             d:DesignHeight="100" d:DesignWidth="200">
+
+    <UserControl.Resources>
+        <converters:BoolToBrushConverter x:Key="BoolToBrushConverter"/>
+        <converters:BrushTuple x:Key="SelectedBrushTuple"
+                               FirstBrush="{StaticResource BrighterAccentColor}" SecondBrush="Transparent"/>
+        <converters:AlternativeTextConverter x:Key="AlternativeTextConveter"/>
+        <converters:AlternativeFontStyleConverter x:Key="AlternativeFontStyleConverter"/>
+    </UserControl.Resources>
+    
+    <Grid>
+        <Grid.ColumnDefinitions>
+            <ColumnDefinition Width="5"/>
+            <ColumnDefinition/>
+        </Grid.ColumnDefinitions>
+        <Grid Background="{Binding IsSelected, Converter={StaticResource BoolToBrushConverter}, ConverterParameter={StaticResource SelectedBrushTuple}, ElementName=uc}"/>
+        <Grid Grid.Column="1" Margin="5,0,0,0">
+            <Grid.RowDefinitions>
+                <RowDefinition Height="Auto"/>
+                <RowDefinition/>
+            </Grid.RowDefinitions>
+            <TextBlock Grid.Row="0"
+                       Text="{Binding Extension.DisplayName, ElementName=uc, Converter={StaticResource AlternativeTextConveter}, ConverterParameter={}Unnamed extension}"
+                       FontStyle="{Binding Extension.DisplayName, ElementName=uc, Converter={StaticResource AlternativeFontStyleConverter}, ConverterParameter=Italic}"
+                       Foreground="White" FontWeight="Bold" FontSize="13"
+                       Margin="0,5,0,0"
+                       TextTrimming="CharacterEllipsis" TextWrapping="Wrap"/>
+            <TextBlock Grid.Row="1"
+                       Text="{Binding Extension.Description, ElementName=uc, Converter={StaticResource AlternativeTextConveter}, ConverterParameter={}No description provided}"
+                       FontStyle="{Binding Extension.Description, ElementName=uc, Converter={StaticResource AlternativeFontStyleConverter}, ConverterParameter=Italic}"
+                       Foreground="White"
+                       Margin="5,5,0,10"
+                       TextWrapping="Wrap" TextTrimming="WordEllipsis"/>
+        </Grid>
+
+        <Grid.InputBindings>
+            <MouseBinding Gesture="LeftClick"
+                                              Command="{Binding DataContext.SelectExtensionCommand, RelativeSource={RelativeSource AncestorType={x:Type Window}}}" 
+                                              CommandParameter="{Binding}"/>
+        </Grid.InputBindings>
+
+        <Grid.Style>
+            <Style TargetType="Grid">
+                <Style.Triggers>
+                    <Trigger Property="IsMouseOver" Value="False">
+                        <Setter Property="Background" Value="Transparent"/>
+                    </Trigger>
+                    <Trigger Property="IsMouseOver" Value="True">
+                        <Setter Property="Background" Value="{StaticResource DarkerAccentColor}"/>
+                    </Trigger>
+                </Style.Triggers>
+            </Style>
+        </Grid.Style>
+    </Grid>
+</UserControl>

+ 51 - 0
PixiEditor/Views/UserControls/ExtensionItem.xaml.cs

@@ -0,0 +1,51 @@
+using PixiEditor.SDK;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using System.Windows;
+using System.Windows.Controls;
+using System.Windows.Data;
+using System.Windows.Documents;
+using System.Windows.Input;
+using System.Windows.Media;
+using System.Windows.Media.Imaging;
+using System.Windows.Navigation;
+using System.Windows.Shapes;
+
+namespace PixiEditor.Views.UserControls
+{
+    /// <summary>
+    /// Interaction logic for ExtensionItem.xaml
+    /// </summary>
+    public partial class ExtensionItem : UserControl
+    {
+        public static readonly DependencyProperty SelectedExtensionProperty =
+            DependencyProperty.Register(nameof(SelectedExtension), typeof(Extension), typeof(ExtensionItem), new PropertyMetadata(SelectedExtensionChanged));
+
+        public Extension SelectedExtension { get => (Extension)GetValue(SelectedExtensionProperty); set => SetValue(SelectedExtensionProperty, value); }
+
+        public static readonly DependencyProperty ExtensionProperty =
+            DependencyProperty.Register(nameof(Extension), typeof(Extension), typeof(ExtensionItem), new PropertyMetadata(SelectedExtensionChanged));
+
+        public Extension Extension { get => (Extension)GetValue(ExtensionProperty); set => SetValue(ExtensionProperty, value); }
+
+        public static readonly DependencyProperty IsSelectedProperty =
+            DependencyProperty.Register(nameof(IsSelected), typeof(bool), typeof(ExtensionItem));
+
+        public bool IsSelected { get => (bool)GetValue(IsSelectedProperty); set => SetValue(IsSelectedProperty, value); }
+
+        public ExtensionItem()
+        {
+            InitializeComponent();
+        }
+
+        private static void SelectedExtensionChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
+        {
+            Extension extension = (Extension)d.GetValue(ExtensionProperty);
+
+            d.SetValue(IsSelectedProperty, e.NewValue == extension);
+        }
+    }
+}

+ 60 - 0
PixiEditor/Views/UserControls/ExtensionPage.xaml

@@ -0,0 +1,60 @@
+<UserControl x:Class="PixiEditor.Views.UserControls.ExtensionPage"
+             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
+             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
+             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
+             xmlns:local="clr-namespace:PixiEditor.Views.UserControls"
+             xmlns:sdk="clr-namespace:PixiEditor.SDK;assembly=PixiEditor.SDK" xmlns:extensions="clr-namespace:PixiEditor.Models.Extensions"
+             mc:Ignorable="d" Foreground="White"
+             d:DesignHeight="300" d:DesignWidth="400">
+    <d:UserControl.DataContext>
+        <extensions:XAMLSDKExtension XAMLDisplayName="Hi" IconSource="../../Images/PixiEditorLogo.png"/>
+    </d:UserControl.DataContext>
+    
+    <UserControl.Resources>
+        <Style TargetType="Button" BasedOn="{StaticResource DarkRoundButton}"/>
+    </UserControl.Resources>
+
+    <Grid>
+        <Grid.RowDefinitions>
+            <RowDefinition Height="150"/>
+            <RowDefinition/>
+        </Grid.RowDefinitions>
+
+        <Grid>
+            <Grid.ColumnDefinitions>
+                <ColumnDefinition MinWidth="10" MaxWidth="50"/>
+                <ColumnDefinition Width="Auto"/>
+            </Grid.ColumnDefinitions>
+
+            <StackPanel Grid.Column="1" VerticalAlignment="Center">
+                <StackPanel Orientation="Horizontal" Height="40">
+                    <Image Height="40" Width="40" Source="{Binding Icon}"/>
+                    <StackPanel Margin="20,-2,20,0">
+                        <StackPanel Orientation="Horizontal">
+                            <TextBlock VerticalAlignment="Center" Text="{Binding DisplayName}" FontSize="20" FontWeight="Bold"/>
+                            <TextBlock VerticalAlignment="Center" Text="{Binding Name}" FontSize="15" Margin="20,0,0,0"
+                                       Foreground="Gray" FontStyle="Italic"/>
+                        </StackPanel>
+                        <local:PrependTextBlock Prepend="v" Text="{Binding Version}"/>
+                    </StackPanel>
+                </StackPanel>
+                <StackPanel Orientation="Horizontal" Margin="0,15,5,5">
+                    <Button FontSize="13" Width="80">
+                        <TextBlock Margin="0,4" Text="Disable"/>
+                    </Button>
+                    <Button FontSize="13" Width="80" Margin="5,0">
+                        <TextBlock Margin="0,4" Text="Remove"/>
+                    </Button>
+                </StackPanel>
+            </StackPanel>
+        </Grid>
+        <ContentPresenter Margin="10" Content="{Binding ExtensionPage}" Grid.Row="1">
+            <ContentPresenter.Resources>
+                <Style TargetType="TextBlock">
+                    <Setter Property="TextWrapping" Value="Wrap"/>
+                </Style>
+            </ContentPresenter.Resources>
+        </ContentPresenter>
+    </Grid>
+</UserControl>

+ 40 - 0
PixiEditor/Views/UserControls/ExtensionPage.xaml.cs

@@ -0,0 +1,40 @@
+using PixiEditor.SDK;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using System.Windows;
+using System.Windows.Controls;
+using System.Windows.Data;
+using System.Windows.Documents;
+using System.Windows.Input;
+using System.Windows.Media;
+using System.Windows.Media.Imaging;
+using System.Windows.Navigation;
+using System.Windows.Shapes;
+
+namespace PixiEditor.Views.UserControls
+{
+    /// <summary>
+    /// Interaction logic for ExtensionPage.xaml
+    /// </summary>
+    public partial class ExtensionPage : UserControl
+    {
+        //public static readonly DependencyProperty ExtensionProperty =
+        //    DependencyProperty.Register(nameof(Extension), typeof(Extension), typeof(ExtensionPage), new PropertyMetadata(ExtensionChanged));
+
+        //public Extension Extension { get => (Extension)GetValue(ExtensionProperty); set => SetValue(ExtensionProperty, value); }
+
+        public ExtensionPage()
+        {
+            InitializeComponent();
+        }
+
+        private static void ExtensionChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
+        {
+            ExtensionPage c = d as ExtensionPage;
+            c.DataContext = e.NewValue;
+        }
+    }
+}