Browse Source

Implement multiple documents and loading

Equbuxu 3 years ago
parent
commit
1bf7a56254

+ 24 - 3
src/PixiEditorPrototype/Models/DocumentStructureHelper.cs

@@ -17,7 +17,8 @@ internal class DocumentStructureHelper
 
     public void CreateNewStructureMember(StructureMemberType type)
     {
-        if (doc.SelectedStructureMember is null)
+        var member = doc.FindFirstSelectedMember();
+        if (member is null)
         {
             var guid = Guid.NewGuid();
             //put member on top
@@ -25,7 +26,7 @@ internal class DocumentStructureHelper
             helpers.ActionAccumulator.AddFinishedActions(new StructureMemberName_Action(guid, type == StructureMemberType.Layer ? "New Layer" : "New Folder"));
             return;
         }
-        if (doc.SelectedStructureMember is FolderViewModel folder)
+        if (member is FolderViewModel folder)
         {
             var guid = Guid.NewGuid();
             //put member inside folder on top
@@ -33,7 +34,7 @@ internal class DocumentStructureHelper
             helpers.ActionAccumulator.AddFinishedActions(new StructureMemberName_Action(guid, type == StructureMemberType.Layer ? "New Layer" : "New Folder"));
             return;
         }
-        if (doc.SelectedStructureMember is LayerViewModel layer)
+        if (member is LayerViewModel layer)
         {
             var guid = Guid.NewGuid();
             //put member above the layer
@@ -55,6 +56,26 @@ internal class DocumentStructureHelper
         return list.Count > 0 ? list[0] : null;
     }
 
+    public StructureMemberViewModel? FindFirstWhere(Predicate<StructureMemberViewModel> predicate)
+    {
+        return FindFirstWhere(predicate, doc.StructureRoot);
+    }
+    private StructureMemberViewModel? FindFirstWhere(Predicate<StructureMemberViewModel> predicate, FolderViewModel folderVM)
+    {
+        foreach (var child in folderVM.Children)
+        {
+            if (predicate(child))
+                return child;
+            if (child is FolderViewModel innerFolderVM)
+            {
+                var result = FindFirstWhere(predicate, innerFolderVM);
+                if (result is not null)
+                    return result;
+            }
+        }
+        return null;
+    }
+
     public (StructureMemberViewModel, FolderViewModel) FindChildAndParentOrThrow(Guid childGuid)
     {
         var path = FindPath(childGuid);

+ 1 - 0
src/PixiEditorPrototype/PixiEditorPrototype.csproj

@@ -12,6 +12,7 @@
     <PackageReference Include="Dirkster.AvalonDock" Version="4.70.1" />
     <PackageReference Include="Microsoft.Xaml.Behaviors.Wpf" Version="1.1.39" />
     <PackageReference Include="PixiEditor.ColorPicker" Version="3.2.0" />
+    <PackageReference Include="PixiEditor.Parser" Version="2.1.0.3" />
   </ItemGroup>
 
   <ItemGroup>

+ 85 - 52
src/PixiEditorPrototype/ViewModels/DocumentViewModel.cs

@@ -2,7 +2,6 @@
 using System.Collections.Generic;
 using System.ComponentModel;
 using System.Linq;
-using System.Windows;
 using System.Windows.Media;
 using System.Windows.Media.Imaging;
 using ChunkyImageLib;
@@ -11,6 +10,7 @@ using Microsoft.Win32;
 using PixiEditor.ChangeableDocument.Actions.Undo;
 using PixiEditor.ChangeableDocument.Changeables.Interfaces;
 using PixiEditor.ChangeableDocument.Enums;
+using PixiEditor.Parser;
 using PixiEditorPrototype.CustomControls.SymmetryOverlay;
 using PixiEditorPrototype.Models;
 using SkiaSharp;
@@ -102,21 +102,23 @@ internal class DocumentViewModel : INotifyPropertyChanged
         set => Helpers.ActionAccumulator.AddFinishedActions(new SymmetryAxisState_Action(SymmetryAxisDirection.Vertical, value));
     }
 
-    public Guid GuidValue { get; } = Guid.NewGuid();
-    public int Width => size.X;
-    public int Height => size.Y;
-
-    private StructureMemberViewModel? selectedStructureMember;
-    public StructureMemberViewModel? SelectedStructureMember
+    private string name = string.Empty;
+    public string Name
     {
-        get => selectedStructureMember;
-        private set
+        get => name;
+        set
         {
-            selectedStructureMember = value;
-            PropertyChanged?.Invoke(this, new(nameof(SelectedStructureMember)));
+            name = value;
+            RaisePropertyChanged(nameof(Name));
         }
     }
 
+    public StructureMemberViewModel? SelectedStructureMember => FindFirstSelectedMember();
+
+    public Guid GuidValue { get; } = Guid.NewGuid();
+    public int Width => size.X;
+    public int Height => size.Y;
+
     public Dictionary<ChunkResolution, WriteableBitmap> Bitmaps { get; set; } = new()
     {
         [ChunkResolution.Full] = new WriteableBitmap(64, 64, 96, 96, PixelFormats.Pbgra32, null),
@@ -133,6 +135,7 @@ internal class DocumentViewModel : INotifyPropertyChanged
 
 
     private DocumentHelpers Helpers { get; }
+
     private ViewModelMain owner;
 
 
@@ -162,10 +165,10 @@ internal class DocumentViewModel : INotifyPropertyChanged
     private int lastEllipseStrokeWidth = 0;
     private RectI lastEllipseLocation = RectI.Empty;
 
-    public DocumentViewModel(ViewModelMain owner)
+    public DocumentViewModel(ViewModelMain owner, string name)
     {
         this.owner = owner;
-
+        Name = name;
         TransformViewModel = new();
         TransformViewModel.TransformMoved += OnTransformUpdate;
 
@@ -178,7 +181,6 @@ internal class DocumentViewModel : INotifyPropertyChanged
         CreateNewLayerCommand = new RelayCommand(_ => Helpers.StructureHelper.CreateNewStructureMember(StructureMemberType.Layer));
         CreateNewFolderCommand = new RelayCommand(_ => Helpers.StructureHelper.CreateNewStructureMember(StructureMemberType.Folder));
         DeleteStructureMemberCommand = new RelayCommand(DeleteStructureMember);
-        ChangeSelectedItemCommand = new RelayCommand(ChangeSelectedItem);
         ResizeCanvasCommand = new RelayCommand(ResizeCanvas);
         CombineCommand = new RelayCommand(Combine);
         ClearHistoryCommand = new RelayCommand(ClearHistory);
@@ -201,31 +203,60 @@ internal class DocumentViewModel : INotifyPropertyChanged
                 bitmap.Value.BackBuffer, bitmap.Value.BackBufferStride);
             Surfaces[bitmap.Key] = surface;
         }
+    }
 
-        Helpers.ActionAccumulator.AddFinishedActions
-            (new CreateStructureMember_Action(StructureRoot.GuidValue, Guid.NewGuid(), 0, StructureMemberType.Layer));
+    public static DocumentViewModel FromSerializableDocument(ViewModelMain owner, SerializableDocument serDocument, string name)
+    {
+        DocumentViewModel document = new DocumentViewModel(owner, name);
+        var acc = document.Helpers.ActionAccumulator;
+        acc.AddActions(new ResizeCanvas_Action(new(serDocument.Width, serDocument.Height)));
+        int index = 0;
+        foreach (var layer in serDocument.Layers.Reverse())
+        {
+            var guid = Guid.NewGuid();
+            var png = SKBitmap.Decode(layer.PngBytes);
+            Surface surface = new(new VecI(layer.Width, layer.Height));
+            surface.SkiaSurface.Canvas.DrawBitmap(png, 0, 0);
+            acc.AddFinishedActions(
+                new CreateStructureMember_Action(document.StructureRoot.GuidValue, guid, index, StructureMemberType.Layer),
+                new StructureMemberName_Action(guid, layer.Name),
+                new PasteImage_Action(surface, new(new RectD(new VecD(layer.OffsetX, layer.OffsetY), new(layer.Width, layer.Height))), guid, true, false),
+                new EndPasteImage_Action()
+                );
+            if (layer.Opacity != 1)
+                acc.AddFinishedActions(
+                    new StructureMemberOpacity_Action(guid, layer.Opacity),
+                    new EndStructureMemberOpacity_Action());
+            if (!layer.IsVisible)
+                acc.AddFinishedActions(new StructureMemberIsVisible_Action(layer.IsVisible, guid));
+        }
+        acc.AddActions(new DeleteRecordedChanges_Action());
+        return document;
     }
 
+    public StructureMemberViewModel? FindFirstSelectedMember() => Helpers.StructureHelper.FindFirstWhere(member => member.IsSelected);
+
     private bool CanStartUpdate()
     {
-        if (SelectedStructureMember is null)
+        var member = FindFirstSelectedMember();
+        if (member is null)
             return false;
-        bool drawOnMask = SelectedStructureMember.ShouldDrawOnMask;
+        bool drawOnMask = member.ShouldDrawOnMask;
         if (!drawOnMask)
         {
-            if (SelectedStructureMember is FolderViewModel)
+            if (member is FolderViewModel)
                 return false;
-            if (SelectedStructureMember is LayerViewModel)
+            if (member is LayerViewModel)
                 return true;
         }
 
-        if (!SelectedStructureMember.HasMaskBindable)
+        if (!member.HasMaskBindable)
             return false;
         return true;
     }
     private void TransformSelectedArea(object? obj)
     {
-        if (updateableChangeActive || SelectedStructureMember is not LayerViewModel layer || SelectionPathBindable.IsEmpty)
+        if (updateableChangeActive || FindFirstSelectedMember() is not LayerViewModel layer || SelectionPathBindable.IsEmpty)
             return;
         IReadOnlyChunkyImage? layerImage = (Helpers.Tracker.Document.FindMember(layer.GuidValue) as IReadOnlyLayer)?.LayerImage;
         if (layerImage is null)
@@ -262,16 +293,15 @@ internal class DocumentViewModel : INotifyPropertyChanged
 
     private void ApplyMask(object? obj)
     {
-        if (updateableChangeActive || SelectedStructureMember is not LayerViewModel layer || !layer.HasMaskBindable)
+        if (updateableChangeActive || FindFirstSelectedMember() is not LayerViewModel layer || !layer.HasMaskBindable)
             return;
         Helpers.ActionAccumulator.AddFinishedActions(new ApplyLayerMask_Action(layer.GuidValue));
     }
 
     private void ClipToMemberBelow(object? obj)
     {
-        if (updateableChangeActive || SelectedStructureMember is null)
+        if (updateableChangeActive || FindFirstSelectedMember() is not StructureMemberViewModel member)
             return;
-        var member = SelectedStructureMember;
         member.ClipToMemberBelowEnabledBindable = !member.ClipToMemberBelowEnabledBindable;
     }
 
@@ -296,12 +326,13 @@ internal class DocumentViewModel : INotifyPropertyChanged
             return;
         updateableChangeActive = true;
         drawingPathBasedPen = true;
+        var member = FindFirstSelectedMember();
         Helpers.ActionAccumulator.AddActions(new PathBasedPen_Action(
-            SelectedStructureMember!.GuidValue,
+            member!.GuidValue,
             pos,
             new SKColor(owner.SelectedColor.R, owner.SelectedColor.G, owner.SelectedColor.B, owner.SelectedColor.A),
             owner.StrokeWidth,
-            SelectedStructureMember.ShouldDrawOnMask));
+            member.ShouldDrawOnMask));
     }
 
     public void EndPathBasedPen()
@@ -319,13 +350,14 @@ internal class DocumentViewModel : INotifyPropertyChanged
             return;
         updateableChangeActive = true;
         drawingLineBasedPen = true;
+        var member = FindFirstSelectedMember();
         Helpers.ActionAccumulator.AddActions(new LineBasedPen_Action(
-            SelectedStructureMember!.GuidValue,
+            member!.GuidValue,
             color,
             pos,
             (int)owner.StrokeWidth,
             replacing,
-            SelectedStructureMember.ShouldDrawOnMask));
+            member.ShouldDrawOnMask));
     }
 
     public void EndLineBasedPen()
@@ -347,13 +379,14 @@ internal class DocumentViewModel : INotifyPropertyChanged
         lastEllipseStrokeWidth = strokeWidth;
         lastEllipseStrokeColor = strokeColor;
         lastEllipseLocation = location;
+        var member = FindFirstSelectedMember();
         Helpers.ActionAccumulator.AddActions(new DrawEllipse_Action(
-            SelectedStructureMember!.GuidValue,
+            member!.GuidValue,
             location,
             strokeColor,
             fillColor,
             strokeWidth,
-            SelectedStructureMember.ShouldDrawOnMask));
+            member.ShouldDrawOnMask));
     }
 
     public void EndEllipse()
@@ -371,7 +404,8 @@ internal class DocumentViewModel : INotifyPropertyChanged
             return;
         updateableChangeActive = true;
         drawingRectangle = true;
-        Helpers.ActionAccumulator.AddActions(new DrawRectangle_Action(SelectedStructureMember!.GuidValue, data, SelectedStructureMember.ShouldDrawOnMask));
+        var member = FindFirstSelectedMember();
+        Helpers.ActionAccumulator.AddActions(new DrawRectangle_Action(member!.GuidValue, data, member.ShouldDrawOnMask));
         lastShape = new ShapeCorners(data.Center, data.Size, data.Angle);
         lastShapeData = data;
     }
@@ -388,7 +422,7 @@ internal class DocumentViewModel : INotifyPropertyChanged
 
     public void StartUpdateShiftLayer(VecI delta)
     {
-        if (SelectedStructureMember is not LayerViewModel layer)
+        if (FindFirstSelectedMember() is not LayerViewModel layer)
             return;
         updateableChangeActive = true;
         shiftingLayer = true;
@@ -502,9 +536,10 @@ internal class DocumentViewModel : INotifyPropertyChanged
         }
         else if (pastingImage)
         {
-            if (SelectedStructureMember is null || pastedImage is null)
+            var member = FindFirstSelectedMember();
+            if (member is null || pastedImage is null)
                 return;
-            Helpers.ActionAccumulator.AddActions(new PasteImage_Action(pastedImage, newCorners, SelectedStructureMember.GuidValue, false, false));
+            Helpers.ActionAccumulator.AddActions(new PasteImage_Action(pastedImage, newCorners, member.GuidValue, false, false));
         }
         else if (transformingSelectionPath)
         {
@@ -514,9 +549,10 @@ internal class DocumentViewModel : INotifyPropertyChanged
 
     public void FloodFill(VecI pos, SKColor color)
     {
-        if (updateableChangeActive || SelectedStructureMember is null)
+        var member = FindFirstSelectedMember();
+        if (updateableChangeActive || member is null)
             return;
-        Helpers.ActionAccumulator.AddFinishedActions(new FloodFill_Action(SelectedStructureMember.GuidValue, pos, color, SelectedStructureMember.ShouldDrawOnMask));
+        Helpers.ActionAccumulator.AddFinishedActions(new FloodFill_Action(member.GuidValue, pos, color, member.ShouldDrawOnMask));
     }
 
     public void AddOrUpdateViewport(ViewportLocation location)
@@ -531,7 +567,7 @@ internal class DocumentViewModel : INotifyPropertyChanged
 
     private void PasteImage(object? args)
     {
-        if (SelectedStructureMember is null || SelectedStructureMember is not LayerViewModel)
+        if (FindFirstSelectedMember() is not LayerViewModel layer)
             return;
         OpenFileDialog dialog = new();
         if (dialog.ShowDialog() != true)
@@ -540,7 +576,7 @@ internal class DocumentViewModel : INotifyPropertyChanged
         pastedImage = Surface.Load(dialog.FileName);
         pastingImage = true;
         ShapeCorners corners = new ShapeCorners(new RectD(VecD.Zero, pastedImage.Size));
-        Helpers.ActionAccumulator.AddActions(new PasteImage_Action(pastedImage, corners, SelectedStructureMember.GuidValue, false, false));
+        Helpers.ActionAccumulator.AddActions(new PasteImage_Action(pastedImage, corners, layer.GuidValue, false, false));
         TransformViewModel.ShowFreeTransform(corners);
     }
 
@@ -553,9 +589,9 @@ internal class DocumentViewModel : INotifyPropertyChanged
 
     private void DeleteStructureMember(object? param)
     {
-        if (updateableChangeActive || SelectedStructureMember is null)
+        if (updateableChangeActive || FindFirstSelectedMember() is not StructureMemberViewModel member)
             return;
-        Helpers.ActionAccumulator.AddFinishedActions(new DeleteStructureMember_Action(SelectedStructureMember.GuidValue));
+        Helpers.ActionAccumulator.AddFinishedActions(new DeleteStructureMember_Action(member.GuidValue));
     }
 
     private void Undo(object? param)
@@ -574,7 +610,7 @@ internal class DocumentViewModel : INotifyPropertyChanged
 
     private void ToggleLockTransparency(object? param)
     {
-        if (updateableChangeActive || SelectedStructureMember is not LayerViewModel layer)
+        if (updateableChangeActive || FindFirstSelectedMember() is not LayerViewModel layer)
             return;
         layer.LockTransparencyBindable = !layer.LockTransparencyBindable;
     }
@@ -586,28 +622,25 @@ internal class DocumentViewModel : INotifyPropertyChanged
         Helpers.ActionAccumulator.AddFinishedActions(new ResizeCanvas_Action(new(ResizeWidth, ResizeHeight)));
     }
 
-    private void ChangeSelectedItem(object? param)
-    {
-        SelectedStructureMember = (StructureMemberViewModel?)((RoutedPropertyChangedEventArgs<object>?)param)?.NewValue;
-    }
-
     private void CreateMask(object? param)
     {
-        if (updateableChangeActive || SelectedStructureMember is null || SelectedStructureMember.HasMaskBindable)
+        var member = FindFirstSelectedMember();
+        if (updateableChangeActive || member is null || member.HasMaskBindable)
             return;
-        Helpers.ActionAccumulator.AddFinishedActions(new CreateStructureMemberMask_Action(SelectedStructureMember.GuidValue));
+        Helpers.ActionAccumulator.AddFinishedActions(new CreateStructureMemberMask_Action(member.GuidValue));
     }
 
     private void DeleteMask(object? param)
     {
-        if (updateableChangeActive || SelectedStructureMember is null || !SelectedStructureMember.HasMaskBindable)
+        var member = FindFirstSelectedMember();
+        if (updateableChangeActive || member is null || !member.HasMaskBindable)
             return;
-        Helpers.ActionAccumulator.AddFinishedActions(new DeleteStructureMemberMask_Action(SelectedStructureMember.GuidValue));
+        Helpers.ActionAccumulator.AddFinishedActions(new DeleteStructureMemberMask_Action(member.GuidValue));
     }
 
     private void Combine(object? param)
     {
-        if (updateableChangeActive || SelectedStructureMember is null)
+        if (updateableChangeActive)
             return;
         List<Guid> selected = new();
         AddSelectedMembers(StructureRoot, selected);

+ 11 - 1
src/PixiEditorPrototype/ViewModels/StructureMemberViewModel.cs

@@ -94,6 +94,7 @@ internal abstract class StructureMemberViewModel : INotifyPropertyChanged
     }
 
     private float opacity;
+
     public void SetOpacity(float opacity)
     {
         this.opacity = opacity;
@@ -104,7 +105,16 @@ internal abstract class StructureMemberViewModel : INotifyPropertyChanged
         get => opacity;
     }
 
-    public bool IsSelected { get; set; }
+    private bool isSelected;
+    public bool IsSelected
+    {
+        get => isSelected;
+        set
+        {
+            isSelected = value;
+            Document.RaisePropertyChanged(nameof(Document.SelectedStructureMember));
+        }
+    }
     public bool ShouldDrawOnMask { get; set; }
 
 

+ 39 - 12
src/PixiEditorPrototype/ViewModels/ViewModelMain.cs

@@ -1,10 +1,13 @@
 using System;
-using System.Collections.Generic;
+using System.Collections.ObjectModel;
 using System.ComponentModel;
+using System.IO;
 using System.Windows.Input;
 using System.Windows.Media;
 using ChunkyImageLib.DataHolders;
+using Microsoft.Win32;
 using PixiEditor.ChangeableDocument.Enums;
+using PixiEditor.Parser;
 using PixiEditor.Zoombox;
 using PixiEditorPrototype.Models;
 using SkiaSharp;
@@ -13,13 +16,14 @@ namespace PixiEditorPrototype.ViewModels;
 
 internal class ViewModelMain : INotifyPropertyChanged
 {
-    public DocumentViewModel? ActiveDocument => GetDocumentByGuid(activeDocumentGuid);
+    public DocumentViewModel? ActiveDocument => ActiveDocumentIndex >= 0 && ActiveDocumentIndex < Documents.Count ? Documents[ActiveDocumentIndex] : null;
 
     public RelayCommand MouseDownCommand { get; }
     public RelayCommand MouseMoveCommand { get; }
     public RelayCommand MouseUpCommand { get; }
     public RelayCommand ChangeActiveToolCommand { get; }
     public RelayCommand SetSelectionModeCommand { get; }
+    public RelayCommand LoadDocumentCommand { get; }
 
     public Color SelectedColor { get; set; } = Colors.Black;
     public bool KeepOriginalImageOnTransform { get; set; } = false;
@@ -59,11 +63,21 @@ internal class ViewModelMain : INotifyPropertyChanged
             PropertyChanged?.Invoke(this, new(nameof(ZoomboxMode)));
         }
     }
+    private int activeDocumentIndex;
+    public int ActiveDocumentIndex
+    {
+        get => activeDocumentIndex;
+        set
+        {
+            activeDocumentIndex = value;
+            PropertyChanged?.Invoke(this, new(nameof(ActiveDocumentIndex)));
+            PropertyChanged?.Invoke(this, new(nameof(ActiveDocument)));
+        }
+    }
 
     public ZoomboxMode ZoomboxMode { get; set; }
 
-    private Dictionary<Guid, DocumentViewModel> documents = new();
-    private Guid activeDocumentGuid;
+    public ObservableCollection<DocumentViewModel> Documents { get; } = new();
 
     private VecD mouseDownCanvasPos;
 
@@ -82,10 +96,9 @@ internal class ViewModelMain : INotifyPropertyChanged
         MouseUpCommand = new RelayCommand(MouseUp);
         ChangeActiveToolCommand = new RelayCommand(ChangeActiveTool);
         SetSelectionModeCommand = new RelayCommand(SetSelectionMode);
+        LoadDocumentCommand = new RelayCommand(LoadDocument);
 
-        var doc = new DocumentViewModel(this);
-        documents[doc.GuidValue] = doc;
-        activeDocumentGuid = doc.GuidValue;
+        Documents.Add(new DocumentViewModel(this, "New Artwork"));
     }
 
     private void SetSelectionMode(object? obj)
@@ -95,11 +108,6 @@ internal class ViewModelMain : INotifyPropertyChanged
         selectionMode = mode;
     }
 
-    public DocumentViewModel? GetDocumentByGuid(Guid guid)
-    {
-        return documents.TryGetValue(guid, out DocumentViewModel? value) ? value : null;
-    }
-
     private void MouseDown(object? param)
     {
         if (ActiveDocument is null || ZoomboxMode != ZoomboxMode.Normal || ActiveDocument.TransformViewModel.TransformActive)
@@ -252,6 +260,25 @@ internal class ViewModelMain : INotifyPropertyChanged
         }
     }
 
+    private void LoadDocument(object? args)
+    {
+        OpenFileDialog dialog = new();
+        if (dialog.ShowDialog() != true)
+            return;
+        SerializableDocument serDocument;
+        try
+        {
+            serDocument = PixiParser.Deserialize(dialog.FileName);
+        }
+        catch (Exception)
+        {
+            return;
+        }
+
+        DocumentViewModel document = DocumentViewModel.FromSerializableDocument(this, serDocument, Path.GetFileName(dialog.FileName));
+        Documents.Add(document);
+    }
+
     private void ChangeActiveTool(object? param)
     {
         if (param is null)

+ 43 - 35
src/PixiEditorPrototype/Views/MainWindow.xaml

@@ -16,6 +16,7 @@
         xmlns:chen="clr-namespace:PixiEditor.ChangeableDocument.Enums;assembly=PixiEditor.ChangeableDocument"
         xmlns:colorpicker="clr-namespace:ColorPicker;assembly=ColorPicker"
         mc:Ignorable="d"
+        x:Name="window"
         Title="MainWindow" Height="800" Width="1500">
     <Window.DataContext>
         <vm:ViewModelMain/>
@@ -78,11 +79,6 @@
                             </Setter>
                         </Style>
                     </TreeView.ItemContainerStyle>
-                    <i:Interaction.Triggers>
-                        <i:EventTrigger EventName="SelectedItemChanged">
-                            <i:InvokeCommandAction Command="{Binding ActiveDocument.ChangeSelectedItemCommand}" PassEventArgsToCommand="True"/>
-                        </i:EventTrigger>
-                    </i:Interaction.Triggers>
                     <TreeView.Resources>
                         <HierarchicalDataTemplate DataType="{x:Type vm:FolderViewModel}" ItemsSource="{Binding Children}">
                             <StackPanel Orientation="Horizontal" MinWidth="200" Background="Wheat">
@@ -180,6 +176,7 @@
         <Border BorderThickness="1" Background="White" BorderBrush="Black" DockPanel.Dock="Top" Margin="5">
             <DockPanel>
                 <StackPanel Orientation="Horizontal" Background="White">
+                    <Button Width="50" Margin="5" Command="{Binding LoadDocumentCommand}">Open</Button>
                     <Button Width="50" Margin="5" Command="{Binding ActiveDocument.UndoCommand}">Undo</Button>
                     <Button Width="50" Margin="5" Command="{Binding ActiveDocument.RedoCommand}">Redo</Button>
                     <Button Width="100" Margin="5" Command="{Binding ActiveDocument.ClearSelectionCommand}">Clear selection</Button>
@@ -237,35 +234,46 @@
                     IsChecked="{Binding ActiveDocument.VerticalSymmetryAxisEnabledBindable}">Ver Sym</CheckBox>
             </StackPanel>
         </Border>
-        <Grid>
-            <Grid.ColumnDefinitions>
-                <ColumnDefinition/>
-                <ColumnDefinition/>
-            </Grid.ColumnDefinitions>
-            <Border BorderThickness="1" BorderBrush="Black" Margin="5">
-                <vp:Viewport
-                    Document="{Binding ActiveDocument}"
-                    FlipX="{Binding ElementName=flipXCheckbox, Path=IsChecked}"
-                    FlipY="{Binding ElementName=flipYCheckbox, Path=IsChecked}"
-                    ZoomMode="{Binding ZoomboxMode, Mode=TwoWay}"
-                    MouseDownCommand="{Binding MouseDownCommand}"
-                    MouseMoveCommand="{Binding MouseMoveCommand}"
-                    MouseUpCommand="{Binding MouseUpCommand}"
-                    Tag="First"
-                    />
-            </Border>
-            <Border BorderThickness="1" BorderBrush="Black" Margin="5" Grid.Column="1">
-                <vp:Viewport
-                    Document="{Binding ActiveDocument}"
-                    FlipX="{Binding ElementName=flipXCheckbox, Path=IsChecked}"
-                    FlipY="{Binding ElementName=flipYCheckbox, Path=IsChecked}"
-                    ZoomMode="{Binding ZoomboxMode, Mode=TwoWay}"
-                    MouseDownCommand="{Binding MouseDownCommand}"
-                    MouseMoveCommand="{Binding MouseMoveCommand}"
-                    MouseUpCommand="{Binding MouseUpCommand}"
-                    Tag="Second"
-                    />
-            </Border>
-        </Grid>
+        <TabControl ItemsSource="{Binding Documents}" SelectedIndex="{Binding ActiveDocumentIndex}">
+            <TabControl.ItemTemplate>
+                <DataTemplate>
+                    <TextBlock Text="{Binding Name}"/>
+                </DataTemplate>
+            </TabControl.ItemTemplate>
+            <TabControl.ContentTemplate>
+                <DataTemplate>
+                    <Grid Background="Gray">
+                        <Grid.ColumnDefinitions>
+                            <ColumnDefinition/>
+                            <ColumnDefinition/>
+                        </Grid.ColumnDefinitions>
+                        <Border BorderThickness="1" BorderBrush="Black" Margin="5">
+                            <vp:Viewport
+                                Document="{Binding}"
+                                FlipX="{Binding ElementName=flipXCheckbox, Path=IsChecked}"
+                                FlipY="{Binding ElementName=flipYCheckbox, Path=IsChecked}"
+                                ZoomMode="{Binding ElementName=window, Path=DataContext.ZoomboxMode, Mode=TwoWay}"
+                                MouseDownCommand="{Binding ElementName=window, Path=DataContext.MouseDownCommand}"
+                                MouseMoveCommand="{Binding ElementName=window, Path=DataContext.MouseMoveCommand}"
+                                MouseUpCommand="{Binding ElementName=window, Path=DataContext.MouseUpCommand}"
+                                Tag="First"
+                            />
+                        </Border>
+                        <Border BorderThickness="1" BorderBrush="Black" Margin="5" Grid.Column="1">
+                            <vp:Viewport
+                                Document="{Binding}"
+                                FlipX="{Binding ElementName=flipXCheckbox, Path=IsChecked}"
+                                FlipY="{Binding ElementName=flipYCheckbox, Path=IsChecked}"
+                                ZoomMode="{Binding ElementName=window, Path=DataContext.ZoomboxMode, Mode=TwoWay}"
+                                MouseDownCommand="{Binding ElementName=window, Path=DataContext.MouseDownCommand}"
+                                MouseMoveCommand="{Binding ElementName=window, Path=DataContext.MouseMoveCommand}"
+                                MouseUpCommand="{Binding ElementName=window, Path=DataContext.MouseUpCommand}"
+                                Tag="Second"
+                            />
+                        </Border>
+                    </Grid>
+                </DataTemplate>
+            </TabControl.ContentTemplate>
+        </TabControl>
     </DockPanel>
 </Window>