mirror of
https://github.com/wabbajack-tools/wabbajack.git
synced 2024-08-30 18:42:17 +00:00
commit
702de2cfbe
@ -60,6 +60,32 @@ namespace Wabbajack
|
||||
.Switch();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Convenience operator to subscribe to the source observable, only when a second "switch" observable is on.
|
||||
/// When the switch is on, the source will be subscribed to, and its updates passed through.
|
||||
/// When the switch is off, the subscription to the source observable will be stopped, and no signal will be published.
|
||||
/// </summary>
|
||||
/// <param name="source">Source observable to subscribe to if on</param>
|
||||
/// <param name="filterSwitch">On/Off signal of whether to subscribe to source observable</param>
|
||||
/// <param name="valueOnOff">Value to fire when switching off</param>
|
||||
/// <returns>Observable that publishes data from source, if the switch is on.</returns>
|
||||
public static IObservable<T> FilterSwitch<T>(this IObservable<T> source, IObservable<bool> filterSwitch, T valueWhenOff)
|
||||
{
|
||||
return filterSwitch
|
||||
.DistinctUntilChanged()
|
||||
.Select(on =>
|
||||
{
|
||||
if (on)
|
||||
{
|
||||
return source;
|
||||
}
|
||||
else
|
||||
{
|
||||
return Observable.Return<T>(valueWhenOff);
|
||||
}
|
||||
})
|
||||
.Switch();
|
||||
}
|
||||
|
||||
/// Inspiration:
|
||||
/// http://reactivex.io/documentation/operators/debounce.html
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -43,13 +43,13 @@ namespace Wabbajack
|
||||
{
|
||||
this.ModlistLocation = new FilePickerVM()
|
||||
{
|
||||
DoExistsCheck = true,
|
||||
ExistCheckOption = FilePickerVM.ExistCheckOptions.On,
|
||||
PathType = FilePickerVM.PathTypeOptions.File,
|
||||
PromptTitle = "Select Modlist"
|
||||
};
|
||||
this.DownloadLocation = new FilePickerVM()
|
||||
{
|
||||
DoExistsCheck = true,
|
||||
ExistCheckOption = FilePickerVM.ExistCheckOptions.On,
|
||||
PathType = FilePickerVM.PathTypeOptions.Folder,
|
||||
PromptTitle = "Select Download Location",
|
||||
};
|
||||
|
@ -36,7 +36,7 @@ namespace Wabbajack
|
||||
this.settings = settings;
|
||||
this.ImagePath = new FilePickerVM()
|
||||
{
|
||||
DoExistsCheck = false,
|
||||
ExistCheckOption = FilePickerVM.ExistCheckOptions.IfNotEmpty,
|
||||
PathType = FilePickerVM.PathTypeOptions.File,
|
||||
Filters =
|
||||
{
|
||||
@ -46,7 +46,7 @@ namespace Wabbajack
|
||||
this.ReadMeText = new FilePickerVM()
|
||||
{
|
||||
PathType = FilePickerVM.PathTypeOptions.File,
|
||||
DoExistsCheck = false,
|
||||
ExistCheckOption = FilePickerVM.ExistCheckOptions.IfNotEmpty,
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -56,19 +56,19 @@ namespace Wabbajack
|
||||
{
|
||||
this.GameLocation = new FilePickerVM()
|
||||
{
|
||||
DoExistsCheck = true,
|
||||
ExistCheckOption = FilePickerVM.ExistCheckOptions.On,
|
||||
PathType = FilePickerVM.PathTypeOptions.Folder,
|
||||
PromptTitle = "Select Game Folder Location"
|
||||
};
|
||||
this.DownloadsLocation = new FilePickerVM()
|
||||
{
|
||||
DoExistsCheck = true,
|
||||
ExistCheckOption = FilePickerVM.ExistCheckOptions.On,
|
||||
PathType = FilePickerVM.PathTypeOptions.Folder,
|
||||
PromptTitle = "Select Downloads Folder"
|
||||
};
|
||||
this.StagingLocation = new FilePickerVM()
|
||||
{
|
||||
DoExistsCheck = true,
|
||||
ExistCheckOption = FilePickerVM.ExistCheckOptions.On,
|
||||
PathType = FilePickerVM.PathTypeOptions.Folder,
|
||||
PromptTitle = "Select Staging Folder"
|
||||
};
|
||||
|
@ -23,6 +23,13 @@ namespace Wabbajack
|
||||
Folder
|
||||
}
|
||||
|
||||
public enum ExistCheckOptions
|
||||
{
|
||||
Off,
|
||||
IfNotEmpty,
|
||||
On
|
||||
}
|
||||
|
||||
public object Parent { get; }
|
||||
|
||||
[Reactive]
|
||||
@ -38,7 +45,7 @@ namespace Wabbajack
|
||||
public PathTypeOptions PathType { get; set; }
|
||||
|
||||
[Reactive]
|
||||
public bool DoExistsCheck { get; set; }
|
||||
public ExistCheckOptions ExistCheckOption { get; set; }
|
||||
|
||||
[Reactive]
|
||||
public IObservable<IErrorResponse> AdditionalError { get; set; }
|
||||
@ -63,28 +70,60 @@ namespace Wabbajack
|
||||
this.SetTargetPathCommand = ConstructTypicalPickerCommand();
|
||||
|
||||
// Check that file exists
|
||||
this._Exists = Observable.Interval(TimeSpan.FromSeconds(3))
|
||||
.FilterSwitch(
|
||||
Observable.CombineLatest(
|
||||
this.WhenAny(x => x.PathType),
|
||||
this.WhenAny(x => x.DoExistsCheck),
|
||||
resultSelector: (type, doExists) => type != PathTypeOptions.Off && doExists))
|
||||
.Unit()
|
||||
// Also do it when fields change
|
||||
.Merge(this.WhenAny(x => x.PathType).Unit())
|
||||
.Merge(this.WhenAny(x => x.DoExistsCheck).Unit())
|
||||
.CombineLatest(
|
||||
this.WhenAny(x => x.DoExistsCheck),
|
||||
this.WhenAny(x => x.PathType),
|
||||
this.WhenAny(x => x.TargetPath)
|
||||
|
||||
var existsCheckTuple = Observable.CombineLatest(
|
||||
this.WhenAny(x => x.ExistCheckOption),
|
||||
this.WhenAny(x => x.PathType),
|
||||
this.WhenAny(x => x.TargetPath)
|
||||
// Dont want to debounce the initial value, because we know it's null
|
||||
.Skip(1)
|
||||
.Debounce(TimeSpan.FromMilliseconds(200)),
|
||||
resultSelector: (_, DoExists, Type, Path) => (DoExists, Type, Path))
|
||||
.Debounce(TimeSpan.FromMilliseconds(200))
|
||||
.StartWith(default(string)),
|
||||
resultSelector: (ExistsOption, Type, Path) => (ExistsOption, Type, Path))
|
||||
.Publish()
|
||||
.RefCount();
|
||||
|
||||
this._Exists = Observable.Interval(TimeSpan.FromSeconds(3))
|
||||
// Only check exists on timer if desired
|
||||
.FilterSwitch(existsCheckTuple
|
||||
.Select(t =>
|
||||
{
|
||||
// Don't do exists type if we don't know what path type we're tracking
|
||||
if (t.Type == PathTypeOptions.Off) return false;
|
||||
switch (t.ExistsOption)
|
||||
{
|
||||
case ExistCheckOptions.Off:
|
||||
return false;
|
||||
case ExistCheckOptions.IfNotEmpty:
|
||||
return !string.IsNullOrWhiteSpace(t.Path);
|
||||
case ExistCheckOptions.On:
|
||||
return true;
|
||||
default:
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}))
|
||||
.Unit()
|
||||
// Also check though, when fields change
|
||||
.Merge(this.WhenAny(x => x.PathType).Unit())
|
||||
.Merge(this.WhenAny(x => x.ExistCheckOption).Unit())
|
||||
.Merge(this.WhenAny(x => x.TargetPath).Unit())
|
||||
// Signaled to check, get latest params for actual use
|
||||
.CombineLatest(existsCheckTuple,
|
||||
resultSelector: (_, tuple) => tuple)
|
||||
// Refresh exists
|
||||
.Select(t =>
|
||||
{
|
||||
if (!t.DoExists) return true;
|
||||
switch (t.ExistsOption)
|
||||
{
|
||||
case ExistCheckOptions.IfNotEmpty:
|
||||
if (string.IsNullOrWhiteSpace(t.Path)) return true;
|
||||
break;
|
||||
case ExistCheckOptions.On:
|
||||
break;
|
||||
case ExistCheckOptions.Off:
|
||||
default:
|
||||
return true;
|
||||
}
|
||||
switch (t.Type)
|
||||
{
|
||||
case PathTypeOptions.Either:
|
||||
|
@ -99,7 +99,7 @@ namespace Wabbajack
|
||||
|
||||
this.Location = new FilePickerVM()
|
||||
{
|
||||
DoExistsCheck = false,
|
||||
ExistCheckOption = FilePickerVM.ExistCheckOptions.Off,
|
||||
PathType = FilePickerVM.PathTypeOptions.Folder,
|
||||
PromptTitle = "Select Installation Directory",
|
||||
};
|
||||
@ -107,7 +107,7 @@ namespace Wabbajack
|
||||
.Select(x => Utils.IsDirectoryPathValid(x));
|
||||
this.DownloadLocation = new FilePickerVM()
|
||||
{
|
||||
DoExistsCheck = false,
|
||||
ExistCheckOption = FilePickerVM.ExistCheckOptions.Off,
|
||||
PathType = FilePickerVM.PathTypeOptions.Folder,
|
||||
PromptTitle = "Select a location for MO2 downloads",
|
||||
};
|
||||
|
@ -1,4 +1,4 @@
|
||||
<UserControl
|
||||
<local:UserControlRx
|
||||
x:Class="Wabbajack.DetailImageView"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
@ -97,7 +97,8 @@
|
||||
FontWeight="Bold"
|
||||
Style="{StaticResource BackgroundBlurStyle}"
|
||||
Text="{Binding Title, Mode=OneWay, RelativeSource={RelativeSource AncestorType={x:Type UserControl}}}"
|
||||
TextWrapping="WrapWithOverflow">
|
||||
TextWrapping="WrapWithOverflow"
|
||||
Visibility="{Binding ShowTitle, Mode=OneWay, RelativeSource={RelativeSource AncestorType={x:Type UserControl}}, Converter={StaticResource bool2VisibilityHiddenConverter}}">
|
||||
<TextBlock.Effect>
|
||||
<BlurEffect Radius="85" />
|
||||
</TextBlock.Effect>
|
||||
@ -113,7 +114,8 @@
|
||||
FontSize="30"
|
||||
FontWeight="Bold"
|
||||
Style="{StaticResource BackgroundBlurStyle}"
|
||||
TextWrapping="WrapWithOverflow">
|
||||
TextWrapping="WrapWithOverflow"
|
||||
Visibility="{Binding ShowAuthor, Mode=OneWay, RelativeSource={RelativeSource AncestorType={x:Type UserControl}}, Converter={StaticResource bool2VisibilityHiddenConverter}}">
|
||||
<TextBlock.Effect>
|
||||
<BlurEffect Radius="55" />
|
||||
</TextBlock.Effect>
|
||||
@ -130,7 +132,8 @@
|
||||
FontSize="65"
|
||||
FontWeight="Bold"
|
||||
Text="{Binding Title, Mode=OneWay, RelativeSource={RelativeSource AncestorType={x:Type UserControl}}}"
|
||||
TextWrapping="WrapWithOverflow">
|
||||
TextWrapping="WrapWithOverflow"
|
||||
Visibility="{Binding ShowTitle, Mode=OneWay, RelativeSource={RelativeSource AncestorType={x:Type UserControl}}, Converter={StaticResource bool2VisibilityHiddenConverter}}">
|
||||
<TextBlock.Effect>
|
||||
<DropShadowEffect />
|
||||
</TextBlock.Effect>
|
||||
@ -142,7 +145,8 @@
|
||||
FontFamily="Lucida Sans"
|
||||
FontSize="30"
|
||||
FontWeight="Bold"
|
||||
TextWrapping="Wrap">
|
||||
TextWrapping="Wrap"
|
||||
Visibility="{Binding ShowAuthor, Mode=OneWay, RelativeSource={RelativeSource AncestorType={x:Type UserControl}}, Converter={StaticResource bool2VisibilityHiddenConverter}}">
|
||||
<TextBlock.Effect>
|
||||
<DropShadowEffect />
|
||||
</TextBlock.Effect>
|
||||
@ -163,7 +167,8 @@
|
||||
Style="{StaticResource BackgroundBlurStyle}"
|
||||
Text="{Binding Description, Mode=OneWay, RelativeSource={RelativeSource AncestorType={x:Type UserControl}}}"
|
||||
TextAlignment="Right"
|
||||
TextWrapping="Wrap">
|
||||
TextWrapping="Wrap"
|
||||
Visibility="{Binding ShowDescription, Mode=OneWay, RelativeSource={RelativeSource AncestorType={x:Type UserControl}}, Converter={StaticResource bool2VisibilityHiddenConverter}}">
|
||||
<TextBlock.Effect>
|
||||
<BlurEffect Radius="55" />
|
||||
</TextBlock.Effect>
|
||||
@ -179,7 +184,8 @@
|
||||
FontSize="16"
|
||||
Text="{Binding Description, Mode=OneWay, RelativeSource={RelativeSource AncestorType={x:Type UserControl}}}"
|
||||
TextAlignment="Right"
|
||||
TextWrapping="Wrap">
|
||||
TextWrapping="Wrap"
|
||||
Visibility="{Binding ShowDescription, Mode=OneWay, RelativeSource={RelativeSource AncestorType={x:Type UserControl}}, Converter={StaticResource bool2VisibilityHiddenConverter}}">
|
||||
<TextBlock.Effect>
|
||||
<DropShadowEffect />
|
||||
</TextBlock.Effect>
|
||||
@ -192,4 +198,4 @@
|
||||
Grid.ColumnSpan="2"
|
||||
Fill="Transparent" />
|
||||
</Grid>
|
||||
</UserControl>
|
||||
</local:UserControlRx>
|
||||
|
@ -1,6 +1,8 @@
|
||||
using System;
|
||||
using ReactiveUI;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reactive.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
@ -18,7 +20,7 @@ namespace Wabbajack
|
||||
/// <summary>
|
||||
/// Interaction logic for DetailImageView.xaml
|
||||
/// </summary>
|
||||
public partial class DetailImageView : UserControl
|
||||
public partial class DetailImageView : UserControlRx
|
||||
{
|
||||
public ImageSource Image
|
||||
{
|
||||
@ -42,7 +44,7 @@ namespace Wabbajack
|
||||
set => SetValue(TitleProperty, value);
|
||||
}
|
||||
public static readonly DependencyProperty TitleProperty = DependencyProperty.Register(nameof(Title), typeof(string), typeof(DetailImageView),
|
||||
new FrameworkPropertyMetadata(default(string), FrameworkPropertyMetadataOptions.BindsTwoWayByDefault));
|
||||
new FrameworkPropertyMetadata(default(string), FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, WireNotifyPropertyChanged));
|
||||
|
||||
public string Author
|
||||
{
|
||||
@ -50,7 +52,7 @@ namespace Wabbajack
|
||||
set => SetValue(AuthorProperty, value);
|
||||
}
|
||||
public static readonly DependencyProperty AuthorProperty = DependencyProperty.Register(nameof(Author), typeof(string), typeof(DetailImageView),
|
||||
new FrameworkPropertyMetadata(default(string), FrameworkPropertyMetadataOptions.BindsTwoWayByDefault));
|
||||
new FrameworkPropertyMetadata(default(string), FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, WireNotifyPropertyChanged));
|
||||
|
||||
public string Description
|
||||
{
|
||||
@ -58,11 +60,32 @@ namespace Wabbajack
|
||||
set => SetValue(DescriptionProperty, value);
|
||||
}
|
||||
public static readonly DependencyProperty DescriptionProperty = DependencyProperty.Register(nameof(Description), typeof(string), typeof(DetailImageView),
|
||||
new FrameworkPropertyMetadata(default(string), FrameworkPropertyMetadataOptions.BindsTwoWayByDefault));
|
||||
new FrameworkPropertyMetadata(default(string), FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, WireNotifyPropertyChanged));
|
||||
|
||||
private readonly ObservableAsPropertyHelper<bool> _ShowAuthor;
|
||||
public bool ShowAuthor => _ShowAuthor.Value;
|
||||
|
||||
private readonly ObservableAsPropertyHelper<bool> _ShowDescription;
|
||||
public bool ShowDescription => _ShowDescription.Value;
|
||||
|
||||
private readonly ObservableAsPropertyHelper<bool> _ShowTitle;
|
||||
public bool ShowTitle => _ShowTitle.Value;
|
||||
|
||||
public DetailImageView()
|
||||
{
|
||||
InitializeComponent();
|
||||
|
||||
this._ShowAuthor = this.WhenAny(x => x.Author)
|
||||
.Select(x => !string.IsNullOrWhiteSpace(x))
|
||||
.ToProperty(this, nameof(this.ShowAuthor));
|
||||
|
||||
this._ShowDescription = this.WhenAny(x => x.Description)
|
||||
.Select(x => !string.IsNullOrWhiteSpace(x))
|
||||
.ToProperty(this, nameof(this.ShowDescription));
|
||||
|
||||
this._ShowTitle = this.WhenAny(x => x.Title)
|
||||
.Select(x => !string.IsNullOrWhiteSpace(x))
|
||||
.ToProperty(this, nameof(this.ShowTitle));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -10,36 +10,90 @@
|
||||
d:DesignWidth="400"
|
||||
BorderBrush="{StaticResource DarkBackgroundBrush}"
|
||||
mc:Ignorable="d">
|
||||
<Grid>
|
||||
<Grid ClipToBounds="True">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="36" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<Border
|
||||
x:Name="BackgroundCornerFillIn"
|
||||
Grid.Column="0"
|
||||
Grid.ColumnSpan="2"
|
||||
Margin="0,0,5,0"
|
||||
Background="{StaticResource TextBoxBackground}"
|
||||
CornerRadius="3" />
|
||||
<TextBox
|
||||
Grid.Column="0"
|
||||
Margin="0,0,-4,0"
|
||||
Margin="5,1,-2,1"
|
||||
VerticalContentAlignment="Center"
|
||||
Background="{StaticResource DarkBackgroundBrush}"
|
||||
Text="{Binding TargetPath, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
|
||||
Visibility="{Binding ShowTextBoxInput}" />
|
||||
<icon:PackIconMaterial
|
||||
Margin="0,4,4,4"
|
||||
Padding="0,3"
|
||||
HorizontalAlignment="Right"
|
||||
VerticalAlignment="Center"
|
||||
Foreground="{StaticResource WarningBrush}"
|
||||
Kind="Circle"
|
||||
ToolTip="{Binding ErrorTooltip}"
|
||||
Visibility="{Binding InError, Converter={StaticResource bool2VisibilityConverter}}" />
|
||||
<Button
|
||||
Grid.Column="1"
|
||||
Command="{Binding SetTargetPathCommand}"
|
||||
ToolTip="Set target path">
|
||||
<icon:PackIconMaterial
|
||||
Width="16"
|
||||
Height="12"
|
||||
Margin="4"
|
||||
Kind="DotsHorizontal" />
|
||||
</Button>
|
||||
<Grid Grid.Column="1" HorizontalAlignment="Right">
|
||||
<Border
|
||||
Margin="3,1,0,1"
|
||||
HorizontalAlignment="Right"
|
||||
Background="{StaticResource WarningBrush}"
|
||||
CornerRadius="3"
|
||||
ToolTip="{Binding ErrorTooltip}">
|
||||
<Border.Style>
|
||||
<Style TargetType="Border">
|
||||
<Setter Property="Width" Value="25" />
|
||||
<Style.Triggers>
|
||||
<DataTrigger Binding="{Binding InError}" Value="True">
|
||||
<DataTrigger.EnterActions>
|
||||
<BeginStoryboard>
|
||||
<Storyboard>
|
||||
<DoubleAnimation
|
||||
Storyboard.TargetProperty="Width"
|
||||
To="33"
|
||||
Duration="0:0:0.1">
|
||||
<DoubleAnimation.EasingFunction>
|
||||
<ExponentialEase EasingMode="EaseOut" />
|
||||
</DoubleAnimation.EasingFunction>
|
||||
</DoubleAnimation>
|
||||
</Storyboard>
|
||||
</BeginStoryboard>
|
||||
</DataTrigger.EnterActions>
|
||||
<DataTrigger.ExitActions>
|
||||
<BeginStoryboard>
|
||||
<Storyboard>
|
||||
<DoubleAnimation
|
||||
Storyboard.TargetProperty="Width"
|
||||
To="25"
|
||||
Duration="0:0:0.1">
|
||||
<DoubleAnimation.EasingFunction>
|
||||
<ExponentialEase EasingMode="EaseOut" />
|
||||
</DoubleAnimation.EasingFunction>
|
||||
</DoubleAnimation>
|
||||
</Storyboard>
|
||||
</BeginStoryboard>
|
||||
</DataTrigger.ExitActions>
|
||||
</DataTrigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
</Border.Style>
|
||||
</Border>
|
||||
<Border
|
||||
Width="30"
|
||||
HorizontalAlignment="Left"
|
||||
Background="{StaticResource TextBoxBackground}"
|
||||
CornerRadius="3">
|
||||
<Button Command="{Binding SetTargetPathCommand}" ToolTip="Set target path">
|
||||
<icon:PackIconMaterial
|
||||
Width="16"
|
||||
Height="12"
|
||||
Margin="4"
|
||||
Kind="DotsHorizontal" />
|
||||
</Button>
|
||||
<Border.Effect>
|
||||
<DropShadowEffect
|
||||
BlurRadius="3"
|
||||
Direction="0"
|
||||
Opacity="0.5"
|
||||
ShadowDepth="2" />
|
||||
</Border.Effect>
|
||||
</Border>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</UserControl>
|
||||
|
@ -85,7 +85,10 @@
|
||||
<Setter Property="FontSize" Value="15" />
|
||||
<Setter Property="Margin" Value="0,0,0,6" />
|
||||
</Style>
|
||||
<Style x:Key="PickerStyle" TargetType="local:FilePicker">
|
||||
<Style
|
||||
x:Key="PickerStyle"
|
||||
BasedOn="{StaticResource MainFilePickerStyle}"
|
||||
TargetType="local:FilePicker">
|
||||
<Setter Property="Margin" Value="0,0,0,6" />
|
||||
</Style>
|
||||
</StackPanel.Resources>
|
||||
|
@ -46,7 +46,7 @@
|
||||
ToolTip="The game you wish to target">
|
||||
<ComboBox.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<TextBlock Text="{Binding DisplayName}" />
|
||||
<TextBlock Margin="6,2" Text="{Binding DisplayName}" />
|
||||
</DataTemplate>
|
||||
</ComboBox.ItemTemplate>
|
||||
</ComboBox>
|
||||
|
Loading…
Reference in New Issue
Block a user