Merge pull request #6 from haven1433/dev

Add support for "Goto"
This commit is contained in:
haven1433
2018-11-10 22:06:13 -06:00
committed by GitHub
15 changed files with 616 additions and 132 deletions

View File

@@ -7,22 +7,33 @@
<!-- Icons accessible using IconExtension -->
<hsv:Icons x:Key="Icons"/>
<!-- Button Style -->
<Style TargetType="Button">
<Setter Property="Padding" Value="3"/>
<Setter Property="BorderBrush" Value="{DynamicResource Backlight}"/>
<Setter Property="Background" Value="{DynamicResource Backlight}"/>
<Setter Property="BorderThickness" Value="1"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Button">
<Border Name="Border"
Padding="{TemplateBinding Padding}"
BorderThickness="{TemplateBinding BorderThickness}"
BorderBrush="{TemplateBinding BorderBrush}"
Background="{TemplateBinding Background}">
<ContentPresenter/>
</Border>
<ControlTemplate.Triggers>
<Trigger SourceName="Border" Property="IsMouseOver" Value="True">
<Setter TargetName="Border" Property="BorderBrush" Value="{solarized:Theme Blue}"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<!-- Scrollbar style. Based on normal style, but with solarize colors. -->
<solarized:ThemeExtension x:Key="ScrollBar.Static.Background" Target="Background"/>
<solarized:ThemeExtension x:Key="ScrollBar.MouseOver.Background" Target="Background"/>
<solarized:ThemeExtension x:Key="ScrollBar.Pressed.Background" Target="Background"/>
<solarized:ThemeExtension x:Key="ScrollBar.Disabled.Background" Target="Background"/>
<solarized:ThemeExtension x:Key="ScrollBar.Pressed.Glyph" Target="Primary"/>
<solarized:ThemeExtension x:Key="ScrollBar.MouseOver.Glyph" Target="Primary"/>
<solarized:ThemeExtension x:Key="ScrollBar.Disabled.Glyph" Target="Primary"/>
<solarized:ThemeExtension x:Key="ScrollBar.Static.Glyph" Target="Primary"/>
<solarized:ThemeExtension x:Key="ScrollBar.Static.Border" Target="Backlight"/>
<solarized:ThemeExtension x:Key="ScrollBar.MouseOver.Border" Target="Backlight"/>
<solarized:ThemeExtension x:Key="ScrollBar.Pressed.Border" Target="Backlight"/>
<solarized:ThemeExtension x:Key="ScrollBar.Disabled.Border" Target="Backlight"/>
<solarized:ThemeExtension x:Key="ScrollBar.MouseOver.Thumb" Target="Secondary"/>
<solarized:ThemeExtension x:Key="ScrollBar.Pressed.Thumb" Target="Secondary"/>
<solarized:ThemeExtension x:Key="ScrollBar.Static.Thumb" Target="Secondary"/>
<Style x:Key="FocusVisual">
<Setter Property="Control.Template">
<Setter.Value>
@@ -43,22 +54,22 @@
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type RepeatButton}">
<Border x:Name="border" BorderBrush="{StaticResource ScrollBar.Static.Border}" BorderThickness="1" Background="{StaticResource ScrollBar.Static.Background}" SnapsToDevicePixels="true">
<Border x:Name="border" BorderBrush="{DynamicResource Backlight}" BorderThickness="1" Background="{DynamicResource Background}" SnapsToDevicePixels="true">
<ContentPresenter x:Name="contentPresenter" Focusable="False" HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" Margin="{TemplateBinding Padding}" SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" VerticalAlignment="{TemplateBinding VerticalContentAlignment}"/>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsMouseOver" Value="true">
<Setter Property="Background" TargetName="border" Value="{StaticResource ScrollBar.MouseOver.Background}"/>
<Setter Property="BorderBrush" TargetName="border" Value="{StaticResource ScrollBar.MouseOver.Border}"/>
<Setter Property="Background" TargetName="border" Value="{DynamicResource Background}"/>
<Setter Property="BorderBrush" TargetName="border" Value="{DynamicResource Backlight}"/>
</Trigger>
<Trigger Property="IsPressed" Value="true">
<Setter Property="Background" TargetName="border" Value="{StaticResource ScrollBar.Pressed.Background}"/>
<Setter Property="BorderBrush" TargetName="border" Value="{StaticResource ScrollBar.Pressed.Border}"/>
<Setter Property="Background" TargetName="border" Value="{DynamicResource Background}"/>
<Setter Property="BorderBrush" TargetName="border" Value="{DynamicResource Backlight}"/>
</Trigger>
<Trigger Property="IsEnabled" Value="false">
<Setter Property="Opacity" TargetName="contentPresenter" Value="0.56"/>
<Setter Property="Background" TargetName="border" Value="{StaticResource ScrollBar.Disabled.Background}"/>
<Setter Property="BorderBrush" TargetName="border" Value="{StaticResource ScrollBar.Disabled.Border}"/>
<Setter Property="Background" TargetName="border" Value="{DynamicResource Background}"/>
<Setter Property="BorderBrush" TargetName="border" Value="{DynamicResource Backlight}"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
@@ -84,13 +95,13 @@
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type Thumb}">
<Rectangle x:Name="rectangle" Fill="{StaticResource ScrollBar.Static.Thumb}" Height="{TemplateBinding Height}" SnapsToDevicePixels="True" Width="{TemplateBinding Width}"/>
<Rectangle x:Name="rectangle" Fill="{DynamicResource Secondary}" Height="{TemplateBinding Height}" SnapsToDevicePixels="True" Width="{TemplateBinding Width}"/>
<ControlTemplate.Triggers>
<Trigger Property="IsMouseOver" Value="true">
<Setter Property="Fill" TargetName="rectangle" Value="{StaticResource ScrollBar.MouseOver.Thumb}"/>
<Setter Property="Fill" TargetName="rectangle" Value="{DynamicResource Secondary}"/>
</Trigger>
<Trigger Property="IsDragging" Value="true">
<Setter Property="Fill" TargetName="rectangle" Value="{StaticResource ScrollBar.Pressed.Thumb}"/>
<Setter Property="Fill" TargetName="rectangle" Value="{DynamicResource Secondary}"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
@@ -103,13 +114,13 @@
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type Thumb}">
<Rectangle x:Name="rectangle" Fill="{StaticResource ScrollBar.Static.Thumb}" Height="{TemplateBinding Height}" SnapsToDevicePixels="True" Width="{TemplateBinding Width}"/>
<Rectangle x:Name="rectangle" Fill="{DynamicResource Secondary}" Height="{TemplateBinding Height}" SnapsToDevicePixels="True" Width="{TemplateBinding Width}"/>
<ControlTemplate.Triggers>
<Trigger Property="IsMouseOver" Value="true">
<Setter Property="Fill" TargetName="rectangle" Value="{StaticResource ScrollBar.MouseOver.Thumb}"/>
<Setter Property="Fill" TargetName="rectangle" Value="{DynamicResource Secondary}"/>
</Trigger>
<Trigger Property="IsDragging" Value="true">
<Setter Property="Fill" TargetName="rectangle" Value="{StaticResource ScrollBar.Pressed.Thumb}"/>
<Setter Property="Fill" TargetName="rectangle" Value="{DynamicResource Secondary}"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
@@ -119,9 +130,9 @@
<Style TargetType="{x:Type ScrollBar}">
<Setter Property="Stylus.IsPressAndHoldEnabled" Value="false"/>
<Setter Property="Stylus.IsFlicksEnabled" Value="false"/>
<Setter Property="Background" Value="{StaticResource ScrollBar.Static.Background}"/>
<Setter Property="BorderBrush" Value="{StaticResource ScrollBar.Static.Border}"/>
<Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.ControlTextBrushKey}}"/>
<Setter Property="Background" Value="{DynamicResource Background}"/>
<Setter Property="BorderBrush" Value="{DynamicResource Backlight}"/>
<Setter Property="Foreground" Value="{DynamicResource Primary}"/>
<Setter Property="BorderThickness" Value="1,0"/>
<Setter Property="Width" Value="{DynamicResource {x:Static SystemParameters.VerticalScrollBarWidthKey}}"/>
<Setter Property="MinWidth" Value="{DynamicResource {x:Static SystemParameters.VerticalScrollBarWidthKey}}"/>
@@ -136,7 +147,7 @@
</Grid.RowDefinitions>
<Border BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" Background="{TemplateBinding Background}" Grid.Row="1"/>
<RepeatButton x:Name="PART_LineUpButton" Command="{x:Static ScrollBar.LineUpCommand}" IsEnabled="{TemplateBinding IsMouseOver}" Style="{StaticResource ScrollBarButton}">
<Path x:Name="ArrowTop" Data="M 0,4 C0,4 0,6 0,6 0,6 3.5,2.5 3.5,2.5 3.5,2.5 7,6 7,6 7,6 7,4 7,4 7,4 3.5,0.5 3.5,0.5 3.5,0.5 0,4 0,4 z" Fill="{StaticResource ScrollBar.Static.Glyph}" Margin="3,4,3,3" Stretch="Uniform"/>
<Path x:Name="ArrowTop" Data="M 0,4 C0,4 0,6 0,6 0,6 3.5,2.5 3.5,2.5 3.5,2.5 7,6 7,6 7,6 7,4 7,4 7,4 3.5,0.5 3.5,0.5 3.5,0.5 0,4 0,4 z" Fill="{DynamicResource Primary}" Margin="3,4,3,3" Stretch="Uniform"/>
</RepeatButton>
<Track x:Name="PART_Track" IsDirectionReversed="true" IsEnabled="{TemplateBinding IsMouseOver}" Grid.Row="1">
<Track.DecreaseRepeatButton>
@@ -150,7 +161,7 @@
</Track.Thumb>
</Track>
<RepeatButton x:Name="PART_LineDownButton" Command="{x:Static ScrollBar.LineDownCommand}" IsEnabled="{TemplateBinding IsMouseOver}" Grid.Row="2" Style="{StaticResource ScrollBarButton}">
<Path x:Name="ArrowBottom" Data="M 0,2.5 C0,2.5 0,0.5 0,0.5 0,0.5 3.5,4 3.5,4 3.5,4 7,0.5 7,0.5 7,0.5 7,2.5 7,2.5 7,2.5 3.5,6 3.5,6 3.5,6 0,2.5 0,2.5 z" Fill="{StaticResource ScrollBar.Static.Glyph}" Margin="3,4,3,3" Stretch="Uniform"/>
<Path x:Name="ArrowBottom" Data="M 0,2.5 C0,2.5 0,0.5 0,0.5 0,0.5 3.5,4 3.5,4 3.5,4 7,0.5 7,0.5 7,0.5 7,2.5 7,2.5 7,2.5 3.5,6 3.5,6 3.5,6 0,2.5 0,2.5 z" Fill="{DynamicResource Primary}" Margin="3,4,3,3" Stretch="Uniform"/>
</RepeatButton>
</Grid>
<ControlTemplate.Triggers>
@@ -159,32 +170,32 @@
<Condition Binding="{Binding IsMouseOver, ElementName=PART_LineDownButton}" Value="true"/>
<Condition Binding="{Binding IsPressed, ElementName=PART_LineDownButton}" Value="true"/>
</MultiDataTrigger.Conditions>
<Setter Property="Fill" TargetName="ArrowBottom" Value="{StaticResource ScrollBar.Pressed.Glyph}"/>
<Setter Property="Fill" TargetName="ArrowBottom" Value="{DynamicResource Primary}"/>
</MultiDataTrigger>
<MultiDataTrigger>
<MultiDataTrigger.Conditions>
<Condition Binding="{Binding IsMouseOver, ElementName=PART_LineUpButton}" Value="true"/>
<Condition Binding="{Binding IsPressed, ElementName=PART_LineUpButton}" Value="true"/>
</MultiDataTrigger.Conditions>
<Setter Property="Fill" TargetName="ArrowTop" Value="{StaticResource ScrollBar.Pressed.Glyph}"/>
<Setter Property="Fill" TargetName="ArrowTop" Value="{DynamicResource Primary}"/>
</MultiDataTrigger>
<MultiDataTrigger>
<MultiDataTrigger.Conditions>
<Condition Binding="{Binding IsMouseOver, ElementName=PART_LineDownButton}" Value="true"/>
<Condition Binding="{Binding IsPressed, ElementName=PART_LineDownButton}" Value="false"/>
</MultiDataTrigger.Conditions>
<Setter Property="Fill" TargetName="ArrowBottom" Value="{StaticResource ScrollBar.MouseOver.Glyph}"/>
<Setter Property="Fill" TargetName="ArrowBottom" Value="{DynamicResource Primary}"/>
</MultiDataTrigger>
<MultiDataTrigger>
<MultiDataTrigger.Conditions>
<Condition Binding="{Binding IsMouseOver, ElementName=PART_LineUpButton}" Value="true"/>
<Condition Binding="{Binding IsPressed, ElementName=PART_LineUpButton}" Value="false"/>
</MultiDataTrigger.Conditions>
<Setter Property="Fill" TargetName="ArrowTop" Value="{StaticResource ScrollBar.MouseOver.Glyph}"/>
<Setter Property="Fill" TargetName="ArrowTop" Value="{DynamicResource Primary}"/>
</MultiDataTrigger>
<Trigger Property="IsEnabled" Value="false">
<Setter Property="Fill" TargetName="ArrowTop" Value="{StaticResource ScrollBar.Disabled.Glyph}"/>
<Setter Property="Fill" TargetName="ArrowBottom" Value="{StaticResource ScrollBar.Disabled.Glyph}"/>
<Setter Property="Fill" TargetName="ArrowTop" Value="{DynamicResource Primary}"/>
<Setter Property="Fill" TargetName="ArrowBottom" Value="{DynamicResource Primary}"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
@@ -208,7 +219,7 @@
</Grid.ColumnDefinitions>
<Border BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" Background="{TemplateBinding Background}" Grid.Column="1"/>
<RepeatButton x:Name="PART_LineLeftButton" Command="{x:Static ScrollBar.LineLeftCommand}" IsEnabled="{TemplateBinding IsMouseOver}" Style="{StaticResource ScrollBarButton}">
<Path x:Name="ArrowLeft" Data="M 3.18,7 C3.18,7 5,7 5,7 5,7 1.81,3.5 1.81,3.5 1.81,3.5 5,0 5,0 5,0 3.18,0 3.18,0 3.18,0 0,3.5 0,3.5 0,3.5 3.18,7 3.18,7 z" Fill="{StaticResource ScrollBar.Static.Glyph}" Margin="3" Stretch="Uniform"/>
<Path x:Name="ArrowLeft" Data="M 3.18,7 C3.18,7 5,7 5,7 5,7 1.81,3.5 1.81,3.5 1.81,3.5 5,0 5,0 5,0 3.18,0 3.18,0 3.18,0 0,3.5 0,3.5 0,3.5 3.18,7 3.18,7 z" Fill="{DynamicResource Primary}" Margin="3" Stretch="Uniform"/>
</RepeatButton>
<Track x:Name="PART_Track" Grid.Column="1" IsEnabled="{TemplateBinding IsMouseOver}">
<Track.DecreaseRepeatButton>
@@ -222,7 +233,7 @@
</Track.Thumb>
</Track>
<RepeatButton x:Name="PART_LineRightButton" Grid.Column="2" Command="{x:Static ScrollBar.LineRightCommand}" IsEnabled="{TemplateBinding IsMouseOver}" Style="{StaticResource ScrollBarButton}">
<Path x:Name="ArrowRight" Data="M 1.81,7 C1.81,7 0,7 0,7 0,7 3.18,3.5 3.18,3.5 3.18,3.5 0,0 0,0 0,0 1.81,0 1.81,0 1.81,0 5,3.5 5,3.5 5,3.5 1.81,7 1.81,7 z" Fill="{StaticResource ScrollBar.Static.Glyph}" Margin="3" Stretch="Uniform"/>
<Path x:Name="ArrowRight" Data="M 1.81,7 C1.81,7 0,7 0,7 0,7 3.18,3.5 3.18,3.5 3.18,3.5 0,0 0,0 0,0 1.81,0 1.81,0 1.81,0 5,3.5 5,3.5 5,3.5 1.81,7 1.81,7 z" Fill="{DynamicResource Primary}" Margin="3" Stretch="Uniform"/>
</RepeatButton>
</Grid>
<ControlTemplate.Triggers>
@@ -231,32 +242,32 @@
<Condition Binding="{Binding IsMouseOver, ElementName=PART_LineRightButton}" Value="true"/>
<Condition Binding="{Binding IsPressed, ElementName=PART_LineRightButton}" Value="true"/>
</MultiDataTrigger.Conditions>
<Setter Property="Fill" TargetName="ArrowRight" Value="{StaticResource ScrollBar.Pressed.Glyph}"/>
<Setter Property="Fill" TargetName="ArrowRight" Value="{DynamicResource Primary}"/>
</MultiDataTrigger>
<MultiDataTrigger>
<MultiDataTrigger.Conditions>
<Condition Binding="{Binding IsMouseOver, ElementName=PART_LineLeftButton}" Value="true"/>
<Condition Binding="{Binding IsPressed, ElementName=PART_LineLeftButton}" Value="true"/>
</MultiDataTrigger.Conditions>
<Setter Property="Fill" TargetName="ArrowLeft" Value="{StaticResource ScrollBar.Pressed.Glyph}"/>
<Setter Property="Fill" TargetName="ArrowLeft" Value="{DynamicResource Primary}"/>
</MultiDataTrigger>
<MultiDataTrigger>
<MultiDataTrigger.Conditions>
<Condition Binding="{Binding IsMouseOver, ElementName=PART_LineRightButton}" Value="true"/>
<Condition Binding="{Binding IsPressed, ElementName=PART_LineRightButton}" Value="false"/>
</MultiDataTrigger.Conditions>
<Setter Property="Fill" TargetName="ArrowRight" Value="{StaticResource ScrollBar.MouseOver.Glyph}"/>
<Setter Property="Fill" TargetName="ArrowRight" Value="{DynamicResource Primary}"/>
</MultiDataTrigger>
<MultiDataTrigger>
<MultiDataTrigger.Conditions>
<Condition Binding="{Binding IsMouseOver, ElementName=PART_LineLeftButton}" Value="true"/>
<Condition Binding="{Binding IsPressed, ElementName=PART_LineLeftButton}" Value="false"/>
</MultiDataTrigger.Conditions>
<Setter Property="Fill" TargetName="ArrowLeft" Value="{StaticResource ScrollBar.MouseOver.Glyph}"/>
<Setter Property="Fill" TargetName="ArrowLeft" Value="{DynamicResource Primary}"/>
</MultiDataTrigger>
<Trigger Property="IsEnabled" Value="false">
<Setter Property="Fill" TargetName="ArrowLeft" Value="{StaticResource ScrollBar.Disabled.Glyph}"/>
<Setter Property="Fill" TargetName="ArrowRight" Value="{StaticResource ScrollBar.Disabled.Glyph}"/>
<Setter Property="Fill" TargetName="ArrowLeft" Value="{DynamicResource Primary}"/>
<Setter Property="Fill" TargetName="ArrowRight" Value="{DynamicResource Primary}"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
@@ -267,10 +278,9 @@
</Style>
<!-- Menu style -->
<solarized:ThemeExtension x:Key="Menu.Static.Background" Target="Background"/>
<Style TargetType="{x:Type Menu}">
<Setter Property="Background" Value="{StaticResource Menu.Static.Background}"/>
<Setter Property="Foreground" Value="{solarized:Theme Primary}"/>
<Setter Property="Background" Value="{DynamicResource Background}"/>
<Setter Property="Foreground" Value="{DynamicResource Primary}"/>
</Style>
<Style TargetType="{x:Type MenuItem}">
@@ -280,18 +290,16 @@
<Setter.Value>
<ControlTemplate TargetType="{x:Type MenuItem}">
<Grid Name="ItemBorder">
<ContentPresenter Margin="10,2" ContentSource="Header" RecognizesAccessKey="True" TextBlock.Foreground="{solarized:Theme Emphasis}"/>
<Popup x:Name="Popup" Placement="Bottom" IsOpen="{TemplateBinding IsSubmenuOpen}">
<Border x:Name="SubmenuBorder" BorderThickness="1"
Background="{solarized:Theme Background}"
BorderBrush="{solarized:Theme Blue}">
<ContentPresenter Margin="10,2" ContentSource="Header" RecognizesAccessKey="True" TextBlock.Foreground="{DynamicResource Emphasis}"/>
<Popup x:Name="Popup" Placement="Bottom" IsOpen="{TemplateBinding IsSubmenuOpen}" SnapsToDevicePixels="True">
<Border x:Name="SubmenuBorder" BorderThickness="1" Background="{DynamicResource Background}" BorderBrush="{solarized:Theme Blue}">
<StackPanel Name="ItemsPresenter" IsItemsHost="True" KeyboardNavigation.DirectionalNavigation="Cycle"/>
</Border>
</Popup>
</Grid>
<ControlTemplate.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter TargetName="ItemBorder" Property="Background" Value="{solarized:Theme Backlight}" />
<Setter TargetName="ItemBorder" Property="Background" Value="{DynamicResource Backlight}" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
@@ -309,15 +317,15 @@
<ColumnDefinition Width="100"/>
</Grid.ColumnDefinitions>
<ContentPresenter Grid.Column="0" Margin="5,5,0,5" ContentSource="Icon" />
<ContentPresenter Grid.Column="1" Name="MainContent" Margin="10,5" VerticalAlignment="Center" ContentSource="Header" RecognizesAccessKey="True" TextBlock.Foreground="{solarized:Theme Emphasis}" />
<TextBlock Grid.Column="2" VerticalAlignment="Center" Text="{TemplateBinding InputGestureText}" Foreground="{solarized:Theme Secondary}" />
<ContentPresenter Grid.Column="1" Name="MainContent" Margin="10,5" VerticalAlignment="Center" ContentSource="Header" RecognizesAccessKey="True" TextBlock.Foreground="{DynamicResource Emphasis}" />
<TextBlock Grid.Column="2" VerticalAlignment="Center" Text="{TemplateBinding InputGestureText}" Foreground="{DynamicResource Secondary}" />
</Grid>
<ControlTemplate.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter TargetName="ItemBorder" Property="Background" Value="{solarized:Theme Backlight}" />
<Setter TargetName="ItemBorder" Property="Background" Value="{DynamicResource Backlight}" />
</Trigger>
<Trigger Property="IsEnabled" Value="False">
<Setter TargetName="MainContent" Property="TextBlock.Foreground" Value="{solarized:Theme Secondary}" />
<Setter TargetName="MainContent" Property="TextBlock.Foreground" Value="{DynamicResource Secondary}" />
<Setter TargetName="MainContent" Property="TextBlock.FontStyle" Value="Italic" />
</Trigger>
</ControlTemplate.Triggers>
@@ -328,35 +336,56 @@
</Style.Triggers>
</Style>
<Style TargetType="{x:Type Separator}">
<Setter Property="Background" Value="{solarized:Theme Background}"/>
<!-- Separator Style has to specify that it's the style to use for MenuItems. This has something to do with the Menu searching for this key. -->
<Style TargetType="{x:Type Separator}" x:Key="{x:Static MenuItem.SeparatorStyleKey}">
<Setter Property="Background" Value="{DynamicResource Secondary}"/>
<Setter Property="Margin" Value="0,2"/>
<Setter Property="Focusable" Value="false"/>
<Setter Property="BorderBrush" Value="{solarized:Theme Secondary}"/>
<Setter Property="BorderBrush" Value="{DynamicResource Secondary}"/>
</Style>
<!-- TextBlock Style-->
<Style TargetType="TextBlock">
<Setter Property="Foreground" Value="{DynamicResource Primary}"/>
</Style>
<!-- TextBox Style -->
<Style TargetType="TextBox">
<Setter Property="Background" Value="{DynamicResource Backlight}"/>
<Setter Property="BorderBrush" Value="{solarized:Theme Blue}"/>
<Setter Property="Foreground" Value="{DynamicResource Primary}"/>
<Setter Property="SelectionBrush" Value="{solarized:Theme Blue}"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="TextBox">
<Border Name="Border" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" Background="{TemplateBinding Background}">
<ScrollViewer Foreground="{TemplateBinding Foreground}" x:Name="PART_ContentHost" />
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<!-- TabContol Styles -->
<solarized:ThemeExtension x:Key="TabItem.Selected.Background" Target="Background"/>
<solarized:ThemeExtension x:Key="TabItem.Selected.Border" Target="Background"/>
<Style TargetType="{x:Type TabControl}">
<Setter Property="Padding" Value="2"/>
<Setter Property="HorizontalContentAlignment" Value="Center"/>
<Setter Property="VerticalContentAlignment" Value="Center"/>
<Setter Property="Background" Value="{StaticResource TabItem.Selected.Background}"/>
<Setter Property="BorderBrush" Value="{StaticResource TabItem.Selected.Border}"/>
<Setter Property="Background" Value="{DynamicResource Background}"/>
<Setter Property="BorderBrush" Value="{DynamicResource Background}"/>
<Setter Property="BorderThickness" Value="1"/>
<Setter Property="Foreground" Value="{solarized:Theme Primary}"/>
<Setter Property="Foreground" Value="{DynamicResource Primary}"/>
</Style>
<Style TargetType="{x:Type TabItem}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type TabItem}">
<Border Name="OuterBorder" BorderThickness="0,0,1,0" BorderBrush="{solarized:Theme Background}">
<Border Name="OuterBorder" BorderThickness="0,0,1,0" BorderBrush="{DynamicResource Background}">
<!-- used for the 1-pixel spacing between tabs -->
<Border Name="Border" BorderThickness="1,1,1,0" BorderBrush="{solarized:Theme Backlight}" Background="{solarized:Theme Backlight}">
<Border Name="Border" BorderThickness="1,1,1,0" BorderBrush="{DynamicResource Backlight}" Background="{DynamicResource Backlight}">
<!-- used for selection -->
<Border Name="InnerBorder" BorderThickness="1,1,1,0" Padding="5,3" BorderBrush="{solarized:Theme Backlight}">
<Border Name="InnerBorder" BorderThickness="1,1,1,0" Padding="5,3" BorderBrush="{DynamicResource Backlight}">
<!-- used for hover-over before selection -->
<ContentPresenter ContentSource="Header" />
</Border>
@@ -364,10 +393,10 @@
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsSelected" Value="True">
<Setter TargetName="Border" Property="Background" Value="{solarized:Theme Background}"/>
<Setter TargetName="Border" Property="Background" Value="{DynamicResource Background}"/>
<Setter TargetName="Border" Property="BorderBrush" Value="{solarized:Theme Blue}"/>
<Setter TargetName="InnerBorder" Property="BorderBrush" Value="{solarized:Theme Blue}"/>
<Setter TargetName="OuterBorder" Property="BorderBrush" Value="{solarized:Theme Background}"/>
<Setter TargetName="OuterBorder" Property="BorderBrush" Value="{DynamicResource Background}"/>
<Setter TargetName="Border" Property="Panel.ZIndex" Value="10"/>
</Trigger>
<Trigger Property="IsMouseOver" Value="True">

View File

@@ -1,5 +1,6 @@
using HavenSoft.Gen3Hex.Model;
using HavenSoft.Gen3Hex.ViewModel;
using System.Collections.Generic;
using System.IO;
using System.Reflection;
using System.Windows;
@@ -10,12 +11,27 @@ namespace HavenSoft.Gen3Hex.View {
public partial class App {
protected override void OnStartup(StartupEventArgs e) {
base.OnStartup(e);
UpdateThemeDictionary();
Solarized.Theme.VariantChanged += (sender, args) => UpdateThemeDictionary();
var fileName = e.Args?.Length == 1 ? e.Args[0] : string.Empty;
var viewPort = GetViewModel(fileName);
MainWindow = new MainWindow(viewPort);
MainWindow.Show();
}
private void UpdateThemeDictionary() {
var dict = new ResourceDictionary {
{ "Emphasis", Solarized.Theme.Emphasis },
{ "Primary", Solarized.Theme.Primary },
{ "Secondary", Solarized.Theme.Secondary },
{ "Background", Solarized.Theme.Background },
{ "Backlight", Solarized.Theme.Backlight },
};
Resources.MergedDictionaries.Clear();
Resources.MergedDictionaries.Add(dict);
}
private EditorViewModel GetViewModel(string fileName) {
var editor = new EditorViewModel(new WindowsFileSystem());
if (!File.Exists(fileName)) return editor;

View File

@@ -11,7 +11,7 @@ using System.Windows.Media;
namespace HavenSoft.Gen3Hex.View {
public class HexContent : FrameworkElement {
public const int CellWidth = 30, CellHeight = 20;
public const double CellWidth = 30, CellHeight = 20;
public static readonly Rect CellRect = new Rect(0, 0, CellWidth, CellHeight);
@@ -152,13 +152,13 @@ namespace HavenSoft.Gen3Hex.View {
}
private void UpdateViewPortSize() {
ViewPort.Width = (int)ActualWidth / CellWidth;
ViewPort.Height = (int)ActualHeight / CellHeight;
ViewPort.Width = (int)(ActualWidth / CellWidth);
ViewPort.Height = (int)(ActualHeight / CellHeight);
}
private Model.Point ControlCoordinatesToModelCoordinates(MouseEventArgs e) {
var point = e.GetPosition(this);
return new Model.Point((int)point.X / CellWidth, (int)point.Y / CellHeight);
return new Model.Point((int)(point.X / CellWidth), (int)(point.Y / CellHeight));
}
}
}

View File

@@ -3,9 +3,13 @@
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:hsg3hv="clr-namespace:HavenSoft.Gen3Hex.View"
xmlns:hsv="clr-namespace:HavenSoft.View;assembly=HavenSoft"
xmlns:sys="clr-namespace:System;assembly=mscorlib"
xmlns:solarized="clr-namespace:Solarized;assembly=HavenSoft"
Icon="..\Snorlax.ico"
Title="Gen3Hex" Height="450" Width="800" AllowDrop="True" Background="{solarized:Theme Background}">
Title="Gen3Hex" Height="450" Width="800" AllowDrop="True" Background="{DynamicResource Background}">
<Window.Resources>
<BooleanToVisibilityConverter x:Key="BoolToVisibility" />
</Window.Resources>
<Window.InputBindings>
<KeyBinding Key="N" Modifiers="Ctrl" Command="{Binding New}" />
<KeyBinding Key="O" Modifiers="Ctrl" Command="{Binding Open}" />
@@ -16,53 +20,95 @@
<KeyBinding Key="Z" Modifiers="Ctrl" Command="{Binding Undo}" />
<KeyBinding Key="Y" Modifiers="Ctrl" Command="{Binding Redo}" />
<KeyBinding Key="Esc" Command="{Binding ClearError}"/>
<KeyBinding Key="G" Modifiers="Ctrl" Command="{Binding ShowGoto}">
<KeyBinding.CommandParameter>
<sys:Boolean>true</sys:Boolean>
</KeyBinding.CommandParameter>
</KeyBinding>
<KeyBinding Key="OemMinus" Modifiers="Ctrl" Command="{Binding Back}" />
<KeyBinding Key="OemMinus" Modifiers="Ctrl+Shift" Command="{Binding Forward}" />
<KeyBinding Key="T" Modifiers="Ctrl" Command="ToggleBullets" /> <!-- Stealing this routed command for toggling theme -->
</Window.InputBindings>
<Window.CommandBindings>
<CommandBinding Command="ToggleBullets" Executed="ToggleTheme"/>
</Window.CommandBindings>
<DockPanel>
<Menu DockPanel.Dock="Top">
<MenuItem Header="_File">
<MenuItem Header="_New" Command="{Binding New}" InputGestureText="Ctrl+N">
<MenuItem.Icon>
<Path Data="{hsv:Icon New}" Fill="{solarized:Theme Secondary}" Stretch="Uniform"/>
</MenuItem.Icon>
<DockPanel DockPanel.Dock="Top">
<StackPanel Orientation="Horizontal" DockPanel.Dock="Right" Visibility="{Binding GotoControlVisible, Converter={StaticResource BoolToVisibility}}">
<TextBlock Text="Goto :" Margin="10,0" />
<TextBox Name="GotoBox" MinWidth="200" IsVisibleChanged="GotoBoxVisibilityChanged">
<TextBox.InputBindings>
<KeyBinding Key="Esc" Command="{Binding ShowGoto}">
<KeyBinding.CommandParameter>
<sys:Boolean>false</sys:Boolean>
</KeyBinding.CommandParameter>
</KeyBinding>
<KeyBinding Key="Return" Command="{Binding Goto}" CommandParameter="{Binding Text, ElementName=GotoBox}"/>
</TextBox.InputBindings>
</TextBox>
</StackPanel>
<StackPanel Orientation="Horizontal" Background="{DynamicResource Backlight}" DockPanel.Dock="Right" Visibility="{Binding ShowError, Converter={StaticResource BoolToVisibility}}">
<TextBlock Margin="10,0" VerticalAlignment="Center" Text="{Binding ErrorMessage}" Foreground="{solarized:Theme Red}"/>
<Button Command="{Binding ClearError}" Width="15">
<Path Data="{hsv:Icon Exit}" Fill="{DynamicResource Secondary}" Stretch="Uniform"/>
</Button>
</StackPanel>
<Menu>
<MenuItem Header="_File">
<MenuItem Header="_New" Command="{Binding New}" InputGestureText="Ctrl+N">
<MenuItem.Icon>
<Path Data="{hsv:Icon New}" Fill="{DynamicResource Secondary}" Stretch="Uniform"/>
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="_Open" Command="{Binding Open}" InputGestureText="Ctrl+O">
<MenuItem.Icon>
<Path Data="{hsv:Icon Open}" Fill="{DynamicResource Secondary}" Stretch="Uniform"/>
</MenuItem.Icon>
</MenuItem>
<Separator />
<MenuItem Header="_Save" Command="{Binding Save}" InputGestureText="Ctrl+S">
<MenuItem.Icon>
<Path Data="{hsv:Icon Save}" Fill="{DynamicResource Secondary}" Stretch="Uniform"/>
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="Save _As" Command="{Binding SaveAs}" InputGestureText="Ctrl+Shift+S" />
<MenuItem Header="Save A_ll" Command="{Binding SaveAll}" InputGestureText="Ctrl+Shift+A" />
<Separator />
<MenuItem Header="_Close Current Tab" Command="{Binding Close}" InputGestureText="Ctrl+W" />
<MenuItem Header="E_xit" Click="ExitClicked" InputGestureText="Alt+F4">
<MenuItem.Icon>
<Path Data="{hsv:Icon Exit}" Fill="{DynamicResource Secondary}" Stretch="Uniform"/>
</MenuItem.Icon>
</MenuItem>
</MenuItem>
<MenuItem Header="_Open" Command="{Binding Open}" InputGestureText="Ctrl+O">
<MenuItem.Icon>
<Path Data="{hsv:Icon Open}" Fill="{solarized:Theme Secondary}" Stretch="Uniform"/>
</MenuItem.Icon>
<MenuItem Header="_Edit">
<MenuItem Header="_Undo" Command="{Binding Undo}" InputGestureText="Ctrl+Z">
<MenuItem.Icon>
<Path Data="{hsv:Icon UndoArrow}" Fill="{DynamicResource Secondary}" Stretch="Uniform"/>
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="_Redo" Command="{Binding Redo}" InputGestureText="Ctrl+Y">
<MenuItem.Icon>
<Path Data="{hsv:Icon RedoArrow}" Fill="{DynamicResource Secondary}" Stretch="Uniform"/>
</MenuItem.Icon>
</MenuItem>
<Separator />
<MenuItem Header="_Goto" Command="GoToPage" InputGestureText="Ctrl+G"/>
<MenuItem Header="Go _Back" Command="{Binding Back}" InputGestureText="Ctrl+-"/>
<MenuItem Header="Go _Forward" Command="{Binding Forward}" InputGestureText="Ctrl+Shift+-"/>
</MenuItem>
<Separator />
<MenuItem Header="_Save" Command="{Binding Save}" InputGestureText="Ctrl+S">
<MenuItem.Icon>
<Path Data="{hsv:Icon Save}" Fill="{solarized:Theme Secondary}" Stretch="Uniform"/>
</MenuItem.Icon>
<MenuItem Header="_View">
<MenuItem Header="_Toggle Theme" Click="ToggleTheme" InputGestureText="Ctrl+T"/>
<MenuItem Header="_Clear Error" Command="{Binding ClearError}" InputGestureText="Esc"/>
</MenuItem>
<MenuItem Header="Save _As" Command="{Binding SaveAs}" InputGestureText="Ctrl+Shift+S" />
<MenuItem Header="Save A_ll" Command="{Binding SaveAll}" InputGestureText="Ctrl+Shift+A" />
<Separator />
<MenuItem Header="_Close Current Tab" Command="{Binding Close}" InputGestureText="Ctrl+W" />
<MenuItem Header="E_xit" Click="ExitClicked" InputGestureText="Alt+F4">
<MenuItem.Icon>
<Path Data="{hsv:Icon Exit}" Fill="{solarized:Theme Secondary}" Stretch="Uniform"/>
</MenuItem.Icon>
</MenuItem>
</MenuItem>
<MenuItem Header="_Edit">
<MenuItem Header="_Undo" Command="{Binding Undo}" InputGestureText="Ctrl+Z">
<MenuItem.Icon>
<Path Data="{hsv:Icon UndoArrow}" Fill="{solarized:Theme Secondary}" Stretch="Uniform"/>
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="_Redo" Command="{Binding Redo}" InputGestureText="Ctrl+Y">
<MenuItem.Icon>
<Path Data="{hsv:Icon RedoArrow}" Fill="{solarized:Theme Secondary}" Stretch="Uniform"/>
</MenuItem.Icon>
</MenuItem>
</MenuItem>
</Menu>
</Menu>
</DockPanel>
<TabControl Name="Tabs" ItemsSource="{Binding}" SelectedIndex="{Binding SelectedIndex}">
<TabControl.ItemTemplate>
<DataTemplate>
<TextBlock Name="TabTextBlock" Background="Transparent" Text="{Binding Name}" Foreground="{solarized:Theme Primary}" MouseDown="TabMouseDown" MouseMove="TabMouseMove" MouseUp="TabMouseUp">
<TextBlock Name="TabTextBlock" Background="Transparent" Text="{Binding Name}" Foreground="{DynamicResource Primary}" MouseDown="TabMouseDown" MouseMove="TabMouseMove" MouseUp="TabMouseUp">
<TextBlock.InputBindings>
<MouseBinding MouseAction="MiddleClick" Command="{Binding Close}"/>
</TextBlock.InputBindings>
@@ -72,10 +118,18 @@
<TabControl.ContentTemplate>
<DataTemplate>
<DockPanel>
<ItemsControl DockPanel.Dock="Left" Width="60" ItemsSource="{Binding Headers}" Background="{DynamicResource Backlight}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBlock Foreground="{DynamicResource Secondary}" Text="{Binding}" FontFamily="Consolas" HorizontalAlignment="Right" Height="{x:Static hsg3hv:HexContent.CellHeight}" Padding="4"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
<Line Width="1" DockPanel.Dock="Left" Stroke="{DynamicResource Background}"/>
<ScrollBar DockPanel.Dock="Right"
Minimum="{Binding MinimumScroll}" Maximum="{Binding MaximumScroll}" Value="{Binding ScrollValue}"
SmallChange="1" LargeChange="{Binding Height}" />
<hsg3hv:HexContent ViewPort="{Binding}"/>
<hsg3hv:HexContent Name="HexContent" ViewPort="{Binding}"/>
</DockPanel>
</DataTemplate>
</TabControl.ContentTemplate>

View File

@@ -93,9 +93,24 @@ namespace HavenSoft.Gen3Hex.View {
#endregion
private void ToggleTheme(object sender, EventArgs e) {
Solarized.Theme.CurrentVariant = 1 - Solarized.Theme.CurrentVariant;
}
private void ExitClicked(object sender, EventArgs e) {
ViewModel.CloseAll.Execute();
if (ViewModel.Count == 0) Close();
}
private void GotoBoxVisibilityChanged(object sender, DependencyPropertyChangedEventArgs e) {
if (GotoBox.IsVisible) {
GotoBox.SelectAll();
Keyboard.Focus(GotoBox);
} else {
if (ViewModel.SelectedIndex == -1) return;
var selectedElement = (HexContent)GetChild(Tabs, "HexContent", ViewModel[ViewModel.SelectedIndex]);
Keyboard.Focus(selectedElement);
}
}
}
}

View File

@@ -11,11 +11,9 @@ namespace HavenSoft.Gen3Hex.ViewModel {
private readonly IFileSystem fileSystem;
private readonly List<ITabContent> tabs;
private readonly StubCommand newCommand, open, save, saveAs, saveAll, close, closeAll, undo, redo;
private readonly StubCommand newCommand, open, save, saveAs, saveAll, close, closeAll, undo, redo, back, forward, gotoCommand, showGoto, clearError;
private readonly Dictionary<Func<ITabContent, ICommand>, EventHandler> forwardExecuteChangeNotifications;
private int selectedIndex;
public ICommand New => newCommand;
public ICommand Open => open;
public ICommand Save => save;
@@ -25,6 +23,36 @@ namespace HavenSoft.Gen3Hex.ViewModel {
public ICommand CloseAll => closeAll;
public ICommand Undo => undo;
public ICommand Redo => redo;
public ICommand Back => back;
public ICommand Forward => forward;
public ICommand Goto => gotoCommand;
public ICommand ShowGoto => showGoto;
public ICommand ClearError => clearError;
private bool gotoControlVisible;
public bool GotoControlVisible {
get => gotoControlVisible;
private set {
TryUpdate(ref gotoControlVisible, value);
if (value) ClearError.Execute();
}
}
private bool showError;
public bool ShowError {
get => showError;
private set {
if (TryUpdate(ref showError, value)) clearError.CanExecuteChanged.Invoke(clearError, EventArgs.Empty);
}
}
private string errorMessage;
public string ErrorMessage {
get => errorMessage;
private set {
if (TryUpdate(ref errorMessage, value)) ShowError = !string.IsNullOrEmpty(ErrorMessage);
}
}
#region Collection Properties
@@ -34,6 +62,7 @@ namespace HavenSoft.Gen3Hex.ViewModel {
public int Count => tabs.Count;
private int selectedIndex;
public int SelectedIndex {
get => selectedIndex;
set {
@@ -66,6 +95,21 @@ namespace HavenSoft.Gen3Hex.ViewModel {
Add(new ViewPort(file));
},
};
gotoCommand = new StubCommand {
CanExecute = arg => SelectedTab?.Goto?.CanExecute(arg) ?? false,
Execute = arg => {
SelectedTab?.Goto?.Execute(arg);
GotoControlVisible = false;
},
};
showGoto = new StubCommand {
CanExecute = CanAlwaysExecute,
Execute = arg => GotoControlVisible = (bool)arg,
};
clearError = new StubCommand {
CanExecute = arg => showError,
Execute = arg => ErrorMessage = string.Empty,
};
save = CreateWrapperForSelected(tab => tab.Save);
saveAs = CreateWrapperForSelected(tab => tab.SaveAs);
saveAll = CreateWrapperForAll(tab => tab.Save);
@@ -73,6 +117,8 @@ namespace HavenSoft.Gen3Hex.ViewModel {
closeAll = CreateWrapperForAll(tab => tab.Close);
undo = CreateWrapperForSelected(tab => tab.Undo);
redo = CreateWrapperForSelected(tab => tab.Redo);
back = CreateWrapperForSelected(tab => tab.Back);
forward = CreateWrapperForSelected(tab => tab.Forward);
forwardExecuteChangeNotifications = new Dictionary<Func<ITabContent, ICommand>, EventHandler> {
{ tab => tab.Save, (sender, e) => save.CanExecuteChanged.Invoke(this, e) },
@@ -80,6 +126,8 @@ namespace HavenSoft.Gen3Hex.ViewModel {
{ tab => tab.Close, (sender, e) => close.CanExecuteChanged.Invoke(this, e) },
{ tab => tab.Undo, (sender, e) => undo.CanExecuteChanged.Invoke(this, e) },
{ tab => tab.Redo, (sender, e) => redo.CanExecuteChanged.Invoke(this, e) },
{ tab => tab.Back, (sender, e) => back.CanExecuteChanged.Invoke(this, e) },
{ tab => tab.Forward, (sender, e) => forward.CanExecuteChanged.Invoke(this, e) },
};
}
@@ -88,6 +136,7 @@ namespace HavenSoft.Gen3Hex.ViewModel {
SelectedIndex = tabs.Count - 1;
CollectionChanged?.Invoke(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, content));
content.Closed += RemoveTab;
content.OnError += AcceptError;
if (content.Save != null) content.Save.CanExecuteChanged += RaiseSaveAllCanExecuteChanged;
}
@@ -163,9 +212,12 @@ namespace HavenSoft.Gen3Hex.ViewModel {
tabs.Remove(tab);
tab.Closed -= RemoveTab;
tab.OnError -= AcceptError;
CollectionChanged?.Invoke(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, tab, index));
}
private void AcceptError(object sender, string message) => ErrorMessage = message;
private void StartListeningToCommandsFromCurrentTab() {
var commandsToRefresh = new List<StubCommand> {
undo,
@@ -173,6 +225,8 @@ namespace HavenSoft.Gen3Hex.ViewModel {
save,
saveAs,
close,
back,
forward,
};
commandsToRefresh.ForEach(command => command.CanExecuteChanged.Invoke(command, EventArgs.Empty));

View File

@@ -11,8 +11,12 @@ namespace HavenSoft.Gen3Hex.ViewModel {
ICommand SaveAs { get; }
ICommand Undo { get; }
ICommand Redo { get; }
ICommand Goto { get; }
ICommand Back { get; }
ICommand Forward { get; }
ICommand Close { get; }
event EventHandler<string> OnError;
event EventHandler Closed;
}
}

View File

@@ -1,8 +1,7 @@
using HavenSoft.Gen3Hex.Model;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Runtime.CompilerServices;
using System.Collections.ObjectModel;
using System.Windows.Input;
namespace HavenSoft.Gen3Hex.ViewModel {
@@ -24,21 +23,28 @@ namespace HavenSoft.Gen3Hex.ViewModel {
get => dataIndex;
private set {
var dif = value - dataIndex;
if (TryUpdate(ref dataIndex, value)) ScrollChanged?.Invoke(this, dif);
if (TryUpdate(ref dataIndex, value)) {
ScrollChanged?.Invoke(this, dif);
UpdateScrollRange();
}
}
}
public int Width {
get => width;
set {
if (TryUpdate(ref width, value.LimitToRange(4, int.MaxValue))) UpdateScrollRange();
if (TryUpdate(ref width, value.LimitToRange(4, int.MaxValue))) {
UpdateScrollRange();
}
}
}
public int Height {
get => height;
set {
if (TryUpdate(ref height, value.LimitToRange(4, int.MaxValue))) UpdateScrollRange();
if (TryUpdate(ref height, value.LimitToRange(4, int.MaxValue))) {
UpdateScrollRange();
}
}
}
@@ -68,6 +74,8 @@ namespace HavenSoft.Gen3Hex.ViewModel {
}
}
public ObservableCollection<string> Headers { get; } = new ObservableCollection<string>();
public event EventHandler<int> ScrollChanged;
public ScrollRegion() {
@@ -77,6 +85,7 @@ namespace HavenSoft.Gen3Hex.ViewModel {
CanExecute = args => dataLength > 0,
Execute = args => ScrollExecuted((Direction)args),
};
UpdateHeaders();
}
public int ViewPointToDataIndex(Point p) => p.Y * width + p.X + dataIndex;
@@ -120,7 +129,6 @@ namespace HavenSoft.Gen3Hex.ViewModel {
ScrollValue += dif.Y;
} else {
DataIndex = (dataIndex + dif.X).LimitToRange(1 - width, dataLength - 1);
UpdateScrollRange();
}
}
@@ -139,6 +147,23 @@ namespace HavenSoft.Gen3Hex.ViewModel {
// Call Update instead of ScrollValue.set to avoid changing the dataIndex.
TryUpdate(ref scrollValue, newCurrentScroll, nameof(ScrollValue));
UpdateHeaders();
}
private void UpdateHeaders() {
while (Headers.Count > Height) Headers.RemoveAt(Headers.Count - 1);
for (int i = 0; i < Height; i++) {
var address = dataIndex + i * Width;
var hexAddress = address.ToString("X6");
if (address >= DataLength) hexAddress = string.Empty;
if (address < 0) hexAddress = string.Empty;
if (Headers.Count > i) {
Headers[i] = hexAddress;
} else {
Headers.Add(hexAddress);
}
}
}
/// <summary>

View File

@@ -1,6 +1,8 @@
using HavenSoft.Gen3Hex.Model;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Globalization;
using System.Runtime.CompilerServices;
using System.Windows.Input;
@@ -9,10 +11,17 @@ namespace HavenSoft.Gen3Hex.ViewModel {
private readonly StubCommand
moveSelectionStart = new StubCommand(),
moveSelectionEnd = new StubCommand();
moveSelectionEnd = new StubCommand(),
gotoCommand = new StubCommand(),
forward = new StubCommand(),
backward = new StubCommand();
private readonly ScrollRegion scroll;
// these back/forward stacks are not encapsulated in a history object because we want to be able to change a remembered address each time we visit it.
// if we navigate back, then scroll, then navigate forward, we want to remember the scroll if we go back again.
private readonly Stack<int> backStack = new Stack<int>(), forwardStack = new Stack<int>();
private Point selectionStart, selectionEnd;
public Point SelectionStart {
@@ -45,8 +54,12 @@ namespace HavenSoft.Gen3Hex.ViewModel {
}
public ICommand MoveSelectionStart => moveSelectionStart;
public ICommand MoveSelectionEnd => moveSelectionEnd;
public ICommand Goto => gotoCommand;
public ICommand Forward => forward;
public ICommand Back => backward;
public event EventHandler<string> OnError;
/// <summary>
/// The owner may have something special going on with the selected point.
@@ -62,6 +75,40 @@ namespace HavenSoft.Gen3Hex.ViewModel {
moveSelectionStart.Execute = args => MoveSelectionStartExecuted((Direction)args);
moveSelectionEnd.CanExecute = args => true;
moveSelectionEnd.Execute = args => MoveSelectionEndExecuted((Direction)args);
gotoCommand = new StubCommand {
CanExecute = args => true,
Execute = args => {
var address = args.ToString();
if (int.TryParse(address, NumberStyles.HexNumber, CultureInfo.CurrentCulture, out int result)) {
backStack.Push(scroll.DataIndex);
forwardStack.Clear();
SelectionStart = scroll.DataIndexToViewPoint(result);
scroll.ScrollValue += selectionStart.Y;
} else {
OnError?.Invoke(this, $"Unable to goto address '{address}'");
}
},
};
backward = new StubCommand {
CanExecute = args => backStack.Count > 0,
Execute = args => {
if (backStack.Count == 0) return;
forwardStack.Push(scroll.DataIndex);
SelectionStart = scroll.DataIndexToViewPoint(backStack.Pop());
scroll.ScrollValue += selectionStart.Y;
},
};
forward = new StubCommand {
CanExecute = args => forwardStack.Count > 0,
Execute = args => {
if (forwardStack.Count == 0) return;
backStack.Push(scroll.DataIndex);
SelectionStart = scroll.DataIndexToViewPoint(forwardStack.Pop());
scroll.ScrollValue += selectionStart.Y;
},
};
}
public bool IsSelected(Point point) {

View File

@@ -2,6 +2,7 @@
using HavenSoft.ViewModel.DataFormats;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Globalization;
@@ -52,7 +53,11 @@ namespace HavenSoft.Gen3Hex.ViewModel {
public int MaximumScroll => scroll.MaximumScroll;
public ObservableCollection<string> Headers => scroll.Headers;
public ICommand Scroll => scroll.Scroll;
public ICommand Goto => selection.Goto;
public ICommand Back => selection.Back;
public ICommand Forward => selection.Forward;
private void ScrollPropertyChanged(object sender, PropertyChangedEventArgs e) {
if (e.PropertyName == nameof(scroll.DataIndex)) {
@@ -188,6 +193,8 @@ namespace HavenSoft.Gen3Hex.ViewModel {
}
}
public event EventHandler<string> OnError;
public event NotifyCollectionChangedEventHandler CollectionChanged;
public ViewPort() : this(new LoadedFile(string.Empty, new byte[0])) { }
@@ -202,6 +209,7 @@ namespace HavenSoft.Gen3Hex.ViewModel {
selection = new Selection(scroll);
selection.PropertyChanged += SelectionPropertyChanged;
selection.PreviewSelectionStartChanged += ClearActiveEditBeforeSelectionChanges;
selection.OnError += (sender, e) => OnError?.Invoke(this, e);
history = new ChangeHistory<Dictionary<int, HexElement>>(RevertChanges);
history.PropertyChanged += HistoryPropertyChanged;

View File

@@ -1,7 +1,7 @@
using System;
using System.Windows.Markup;
using System.Windows.Media;
using System.Xaml;
using static Solarized.Brushes;
using static Solarized.Colors;
@@ -46,7 +46,7 @@ namespace Solarized {
return brush;
}
public static readonly Brush
public static readonly SolidColorBrush
Yellow = Brush(Colors.Yellow),
Orange = Brush(Colors.Orange),
Red = Brush(Colors.Red),
@@ -127,7 +127,20 @@ namespace Solarized {
public ThemeExtension() { }
public ThemeExtension(ThemeProperties target) => Target = target;
public override object ProvideValue(IServiceProvider serviceProvider) {
switch (Target) {
return ThemeColorExtension.GetBrush(Target);
}
}
public sealed class ThemeColorExtension : MarkupExtension {
public ThemeProperties Target { get; set; }
public ThemeColorExtension() { }
public ThemeColorExtension(ThemeProperties target) => Target = target;
public override object ProvideValue(IServiceProvider serviceProvider) {
return GetBrush(Target).Color;
}
public static SolidColorBrush GetBrush(ThemeProperties property) {
switch (property) {
case ThemeProperties.Emphasis: return Theme.Emphasis;
case ThemeProperties.Primary: return Theme.Primary;
case ThemeProperties.Secondary: return Theme.Secondary;

View File

@@ -251,6 +251,47 @@ namespace HavenSoft.HexTests {
Assert.Equal(1, count);
}
[Fact]
public void EditorShowsErrors() {
var tab = new StubTabContent();
editor.Add(tab);
var clearErrorChangedNotifications = 0;
editor.ClearError.CanExecuteChanged += (sender, e) => clearErrorChangedNotifications += 1;
tab.OnError.Invoke(tab, "Some Message");
Assert.True(editor.ShowError);
Assert.Equal(editor.ErrorMessage, "Some Message");
Assert.Equal(1, clearErrorChangedNotifications);
}
[Fact]
public void EditorCanClearErrors() {
var tab = new StubTabContent();
editor.Add(tab);
var clearErrorChangedNotifications = 0;
editor.ClearError.CanExecuteChanged += (sender, e) => clearErrorChangedNotifications += 1;
tab.OnError.Invoke(tab, "Some Message");
editor.ClearError.Execute();
Assert.False(editor.ShowError);
Assert.Equal(editor.ErrorMessage, string.Empty);
Assert.Equal(2, clearErrorChangedNotifications);
}
[Fact]
public void ShowingGotoClearsErrors() {
var tab = new StubTabContent();
editor.Add(tab);
tab.OnError.Invoke(tab, "Some Message");
editor.ShowGoto.Execute(true);
Assert.True(editor.GotoControlVisible);
Assert.False(editor.ShowError);
}
private StubTabContent CreateClosableTab() {
var tab = new StubTabContent();
var close = new StubCommand { CanExecute = arg => true };

View File

@@ -63,6 +63,7 @@
<Compile Include="GeneralAppTests.cs" />
<Compile Include="generatedCode\StubFileSystem.cs" />
<Compile Include="generatedCode\StubTabContent.cs" />
<Compile Include="NavigationTests.cs" />
<Compile Include="ViewPortCursorTests.cs" />
<Compile Include="ViewPortEditTests.cs" />
<Compile Include="ViewPortSaveTests.cs" />

173
HexTests/NavigationTests.cs Normal file
View File

@@ -0,0 +1,173 @@
using HavenSoft.Gen3Hex.Model;
using HavenSoft.Gen3Hex.ViewModel;
using Xunit;
namespace HavenSoft.HexTests {
public class NavigationTests {
[Fact]
public void AddressesAreCorrect() {
var viewPort = new ViewPort(new LoadedFile("test.txt", new byte[0x200])) { Width = 0x10, Height = 0x10 };
Assert.Equal("000000", viewPort.Headers[0]);
Assert.Equal("000010", viewPort.Headers[1]);
Assert.Equal(viewPort.Height, viewPort.Headers.Count);
}
[Fact]
public void AddressUpdateOnWidthChanged() {
var viewPort = new ViewPort(new LoadedFile("test.txt", new byte[0x200])) { Width = 0x10, Height = 0x10 };
var changeCount = 0;
viewPort.Headers.CollectionChanged += (sender, e) => changeCount += 1;
viewPort.Width++;
Assert.InRange(changeCount, 1, viewPort.Height);
Assert.Equal("000011", viewPort.Headers[1]);
}
[Fact]
public void AddressUpdateOnScroll() {
var viewPort = new ViewPort(new LoadedFile("test.txt", new byte[0x200])) { Width = 0x10, Height = 0x10 };
var changeCount = 0;
viewPort.Headers.CollectionChanged += (sender, e) => changeCount += 1;
viewPort.ScrollValue++;
Assert.InRange(changeCount, 1, viewPort.Height);
Assert.Equal("000020", viewPort.Headers[1]);
}
[Fact]
public void AddressExtendOnHeightChanged() {
var viewPort = new ViewPort(new LoadedFile("test.txt", new byte[0x200])) { Width = 0x10, Height = 0x10 };
var changeCount = 0;
viewPort.Headers.CollectionChanged += (sender, e) => changeCount += 1;
viewPort.Height++;
Assert.InRange(changeCount, 1, viewPort.Height);
Assert.Equal(viewPort.Height, viewPort.Headers.Count);
}
[Fact]
public void AddressMissingIfBeforeFile() {
var viewPort = new ViewPort(new LoadedFile("test.txt", new byte[0x200])) { Width = 0x10, Height = 0x10 };
var changeCount = 0;
viewPort.Headers.CollectionChanged += (sender, e) => changeCount += 1;
viewPort.Scroll.Execute(Direction.Left);
Assert.InRange(changeCount, 1, viewPort.Height);
Assert.Equal(string.Empty, viewPort.Headers[0]);
Assert.Equal("00000F", viewPort.Headers[1]);
}
[Fact]
public void AddressMissingIfAfterEndOfFile() {
var viewPort = new ViewPort(new LoadedFile("test.txt", new byte[0x200])) { Width = 0x10, Height = 0x10 };
var changeCount = 0;
viewPort.Headers.CollectionChanged += (sender, e) => changeCount += 1;
viewPort.ScrollValue = viewPort.MaximumScroll;
Assert.InRange(changeCount, 1, viewPort.Height);
Assert.Equal("0001F0", viewPort.Headers[0]);
Assert.Equal(string.Empty, viewPort.Headers[1]);
}
[Fact]
public void AddressAddedIfFileGrows() {
var viewPort = new ViewPort(new LoadedFile("test.txt", new byte[0x200])) { Width = 0x10, Height = 0x10 };
var changeCount = 0;
viewPort.Headers.CollectionChanged += (sender, e) => changeCount += 1;
viewPort.ScrollValue = viewPort.MaximumScroll;
viewPort.SelectionStart = new Point(0, 1);
viewPort.Edit("FF");
Assert.Equal("000200", viewPort.Headers[1]);
}
[Fact]
public void GotoWorks() {
var viewPort = new ViewPort(new LoadedFile("test.txt", new byte[0x1000])) { Width = 0x10, Height = 0x10 };
viewPort.Goto.Execute("000300");
Assert.Equal("000300", viewPort.Headers[0]);
}
[Fact]
public void GotoIsNotCaseSensitive() {
var viewPort = new ViewPort(new LoadedFile("test.txt", new byte[0x1000])) { Width = 0x10, Height = 0x10 };
viewPort.Goto.Execute("000a00");
Assert.Equal("000A00", viewPort.Headers[0]);
}
[Fact]
public void GotoIsReversable() {
var viewPort = new ViewPort(new LoadedFile("test.txt", new byte[0x1000])) { Width = 0x10, Height = 0x10 };
viewPort.Goto.Execute("000A00");
viewPort.Back.Execute();
Assert.Equal("000000", viewPort.Headers[0]);
}
[Fact]
public void BackIsReversable() {
var viewPort = new ViewPort(new LoadedFile("test.txt", new byte[0x1000])) { Width = 0x10, Height = 0x10 };
viewPort.Goto.Execute("000A00");
viewPort.Back.Execute();
viewPort.Forward.Execute();
Assert.Equal("000A00", viewPort.Headers[0]);
}
[Fact]
public void BackRecallsYourLastScrollPosition() {
var viewPort = new ViewPort(new LoadedFile("test.txt", new byte[0x1000])) { Width = 0x10, Height = 0x10 };
viewPort.ScrollValue++;
viewPort.Goto.Execute("000A00");
viewPort.ScrollValue++;
viewPort.Back.Execute();
Assert.Equal("000010", viewPort.Headers[0]);
viewPort.Forward.Execute();
Assert.Equal("000A10", viewPort.Headers[0]);
}
[Fact]
public void GotoMovesScroll() {
var viewPort = new ViewPort(new LoadedFile("test.txt", new byte[0x1000])) { Width = 0x10, Height = 0x10 };
viewPort.Goto.Execute("000A00");
Assert.NotEqual(0, viewPort.ScrollValue);
}
[Fact]
public void GotoErrorsOnBadAddress() {
var viewPort = new ViewPort(new LoadedFile("test.txt", new byte[0x1000])) { Width = 0x10, Height = 0x10 };
int errorCount = 0;
viewPort.OnError += (sender, e) => errorCount += 1;
viewPort.Goto.Execute("BadAddress");
Assert.Equal(1, errorCount);
}
[Fact]
public void GotoMovesSelection() {
var viewPort = new ViewPort(new LoadedFile("test.txt", new byte[0x1000])) { Width = 0x10, Height = 0x10 };
viewPort.Goto.Execute("000C00");
Assert.Equal(new Point(0, 0), viewPort.SelectionStart);
}
}
}

View File

@@ -163,10 +163,14 @@ namespace HavenSoft.HexTests {
public void ScrollingRightUpdatesScrollValueIfNeeded() {
var loadedFile = new LoadedFile("test", new byte[25]);
var viewPort = new ViewPort(loadedFile) { Width = 5, Height = 5 };
var changeCount = 0;
viewPort.CollectionChanged += (sender, e) => changeCount += 1;
viewPort.Scroll.Execute(Direction.Right);
Assert.Equal(1, viewPort.ScrollValue);
Assert.NotEqual(0, changeCount);
}
[Fact]