diff --git a/AcManager.Controls/Assets/AcSettingsSpecific.xaml b/AcManager.Controls/Assets/AcSettingsSpecific.xaml index 187a4583c..36e15a22a 100644 --- a/AcManager.Controls/Assets/AcSettingsSpecific.xaml +++ b/AcManager.Controls/Assets/AcSettingsSpecific.xaml @@ -2,7 +2,7 @@ xmlns:acs="clr-namespace:AcManager.Tools.Helpers.AcSettingsControls;assembly=AcManager.Tools" xmlns:mui="http://firstfloorsoftware.com/ModernUI" xmlns:c="clr-namespace:AcManager.Controls" xmlns:acset="clr-namespace:AcManager.Tools.Helpers.AcSettings;assembly=AcManager.Tools" xmlns:t="http://acstuff.ru/app/tools" xmlns:forms="clr-namespace:System.Windows.Forms;assembly=System.Windows.Forms" - xmlns:helpers="clr-namespace:AcManager.Controls.Helpers"> + xmlns:helpers="clr-namespace:AcManager.Controls.Helpers" xmlns:se="clr-namespace:AcManager.Controls.Services"> @@ -286,8 +286,8 @@ - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/AcManager/Pages/Settings/SettingsShadersPatch.xaml.cs b/AcManager/Pages/Settings/SettingsShadersPatch.xaml.cs new file mode 100644 index 000000000..e1ecc8671 --- /dev/null +++ b/AcManager/Pages/Settings/SettingsShadersPatch.xaml.cs @@ -0,0 +1,236 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.IO; +using System.Linq; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Input; +using System.Windows.Media; +using AcManager.Controls.Helpers; +using AcManager.Tools.Managers; +using AcManager.Tools.Objects; +using AcTools.Utils; +using AcTools.Utils.Helpers; +using FirstFloor.ModernUI; +using FirstFloor.ModernUI.Commands; +using FirstFloor.ModernUI.Helpers; +using FirstFloor.ModernUI.Presentation; +using FirstFloor.ModernUI.Serialization; +using FirstFloor.ModernUI.Windows.Controls; +using JetBrains.Annotations; + +namespace AcManager.Pages.Settings { + public partial class SettingsShadersPatch : ILocalKeyBindings { + public static bool IsCustomShadersPatchInstalled() { + return Directory.Exists(Path.Combine(AcRootDirectory.Instance.RequireValue, "extension", "config")); + } + + public SettingsShadersPatch() { + KeyBindingsController = new LocalKeyBindingsController(this); + /*InputBindings.Add(new InputBinding(new DelegateCommand(() => { + Model.SelectedApp?.ViewInExplorerCommand.Execute(null); + }), new KeyGesture(Key.F, ModifierKeys.Control))); + InputBindings.Add(new InputBinding(new DelegateCommand(() => { + Model.SelectedApp?.ReloadCommand.Execute(null); + }), new KeyGesture(Key.R, ModifierKeys.Control)));*/ + + InitializeComponent(); + DataContext = new ViewModel(false); + Model.PropertyChanged += OnModelPropertyChanged; + SetKeyboardInputs(); + UpdateConfigsTabs(); + this.OnActualUnload(() => { Model?.Dispose(); }); + } + + private void SetKeyboardInputs() { + KeyBindingsController.Set(Model.SelectedConfig?.Sections.SelectMany().OfType()); + } + + private void OnModelPropertyChanged(object sender, PropertyChangedEventArgs e) { + if (e.PropertyName == nameof(Model.SelectedConfig)) { + SetKeyboardInputs(); + UpdateConfigsTabs(); + } + } + + private void UpdateConfigsTabs() { + try { + ConfigTab.Content = Model.SelectedConfig; + } catch (Exception e) { + Logging.Error(e); + } + } + + private ViewModel Model => (ViewModel)DataContext; + + public enum Mode { + NoShadersPatch, + NoConfigs, + NoFittingConfigs, + EverythingIsFine + } + + public class ViewModel : NotifyPropertyChanged, IDisposable { + private readonly bool _isLive; + private StoredValue _selectedConfigId = Stored.Get("__CspAppsSettingsPage.Selected"); + + public ViewModel(bool isLive) { + _isLive = isLive; + _dir = Path.Combine(AcRootDirectory.Instance.RequireValue, "extension", "config"); + _directoryWatcher = Directory.Exists(_dir) ? SimpleDirectoryWatcher.WatchDirectory(_dir, OnDirectoryUpdate) : null; + CreateConfigs(); + } + + private DateTime _lastSaved; + + private void SaveConfigs() { + if (!Directory.Exists(_dir)) return; + _lastSaved = DateTime.Now; + foreach (var config in _configs) { + config.Save(); + } + } + + private readonly Busy _configsSaveBusy = new Busy(); + + private void OnConfigsValueChanged(object sender, EventArgs e) { + _configsSaveBusy.DoDelay(SaveConfigs, 100); + } + + private readonly Busy _busy = new Busy(true); + + private void CreateConfigs() { + if (Configs != null) { + Configs.ValueChanged -= OnConfigsValueChanged; + Configs.Dispose(); + } + + if (!Directory.Exists(_dir)) { + Mode = Mode.NoShadersPatch; + Configs = null; + return; + } + + FileUtils.EnsureDirectoryExists(Path.Combine(AcPaths.GetDocumentsCfgDirectory(), "extension")); + var anyConfigFound = false; + Configs = new PythonAppConfigs(new PythonAppConfigParams(_dir) { + FilesRelativeDirectory = AcRootDirectory.Instance.Value ?? _dir, + ScanFunc = d => Directory.GetFiles(d, "*.ini").Where(x => !Path.GetFileName(x).StartsWith(@"data_")), + ConfigFactory = (p, f) => { + var fileName = Path.GetFileName(f); + if (fileName == null) return null; + anyConfigFound = true; + var userEditedFile = Path.Combine(AcPaths.GetDocumentsCfgDirectory(), "extension", fileName); + + var cfg = PythonAppConfig.Create(p, f, true, userEditedFile); + if (_isLive && cfg.Sections.GetByIdOrDefault("ℹ")?.GetByIdOrDefault("LIVE_SUPPORT")?.Value == @"0") { + return null; + } + + return string.IsNullOrWhiteSpace(cfg.ShortDescription) ? null : cfg; + }, + SaveOnlyNonDefault = true, + Flags = new Dictionary { + [@"IS_LIVE__"] = _isLive.As() + } + }); + + if (Configs.Count > 0) { + Mode = Mode.EverythingIsFine; + } else if (anyConfigFound) { + Mode = Mode.NoFittingConfigs; + } else { + Mode = Mode.NoConfigs; + } + + SelectedConfig = Configs.GetByIdOrDefault(_selectedConfigId.Value) ?? Configs.FirstOrDefault(); + Configs.ValueChanged += OnConfigsValueChanged; + } + + private void OnDirectoryUpdate(string filename) { + _busy.DoDelay(() => { + if ((DateTime.Now - _lastSaved).TotalSeconds < 3d) return; + CreateConfigs(); + }, 300); + } + + private readonly string _dir; + private readonly IDisposable _directoryWatcher; + + private Mode _mode; + + public Mode Mode { + get => _mode; + set => Apply(value, ref _mode); + } + + private PythonAppConfigs _configs; + + [CanBeNull] + public PythonAppConfigs Configs { + get => _configs; + set { + if (Equals(value, _configs)) return; + _configs = value; + OnPropertyChanged(); + } + } + + private PythonAppConfig _selectedConfig; + + [CanBeNull] + public PythonAppConfig SelectedConfig { + get => _selectedConfig; + set { + if (Equals(value, _selectedConfig)) return; + _selectedConfig = value; + if (value?.Id != null) { + _selectedConfigId.Value = value.Id; + } + OnPropertyChanged(); + } + } + + public void Dispose() { + _directoryWatcher?.Dispose(); + Configs?.Dispose(); + } + } + + public LocalKeyBindingsController KeyBindingsController { get; } + + public static ICommand GetShowSettingsCommand() { + return new AsyncCommand(() => { + var dlg = new ModernDialog { + ShowTitle = false, + Content = new SettingsShadersPatchPopup(), + MinHeight = 400, + MinWidth = 450, + MaxHeight = 99999, + MaxWidth = 700, + Padding = new Thickness(0), + ButtonsMargin = new Thickness(8), + SizeToContent = SizeToContent.Manual, + ResizeMode = ResizeMode.NoResize, + WindowStyle = WindowStyle.None, + AllowsTransparency = true, + BlurBackground = true, + ShowTopBlob = false, + Topmost = true, + Title = "Custom Shaders Patch settings", + LocationAndSizeKey = @".CustomShadersPatchDialog", + Owner = null, + Buttons = new Control[0], + BorderThickness = new Thickness(0), + Opacity = 0.9, + BorderBrush = new SolidColorBrush(Colors.Transparent) + }; + + dlg.Background = new SolidColorBrush(((Color)dlg.FindResource("WindowBackgroundColor")).SetAlpha(200)); + + return dlg.ShowAndWaitAsync(); + }); + } + } +} \ No newline at end of file diff --git a/AcManager/Pages/Settings/SettingsShadersPatchPopup.xaml b/AcManager/Pages/Settings/SettingsShadersPatchPopup.xaml new file mode 100644 index 000000000..d27e5b15e --- /dev/null +++ b/AcManager/Pages/Settings/SettingsShadersPatchPopup.xaml @@ -0,0 +1,125 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/AcManager/Pages/Settings/SettingsShadersPatchPopup.xaml.cs b/AcManager/Pages/Settings/SettingsShadersPatchPopup.xaml.cs new file mode 100644 index 000000000..6822e7278 --- /dev/null +++ b/AcManager/Pages/Settings/SettingsShadersPatchPopup.xaml.cs @@ -0,0 +1,203 @@ +using System; +using System.ComponentModel; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Forms; +using System.Windows.Input; +using System.Windows.Media; +using System.Windows.Media.Animation; +using AcManager.Controls.Helpers; +using AcManager.Tools.Objects; +using AcTools.Utils; +using AcTools.Utils.Helpers; +using AcTools.Windows; +using FirstFloor.ModernUI; +using FirstFloor.ModernUI.Helpers; +using FirstFloor.ModernUI.Presentation; +using FirstFloor.ModernUI.Windows; +using FirstFloor.ModernUI.Windows.Controls; +using FirstFloor.ModernUI.Windows.Media; +using JetBrains.Annotations; +using KeyEventArgs = System.Windows.Input.KeyEventArgs; +using ListBox = System.Windows.Controls.ListBox; + +namespace AcManager.Pages.Settings { + public partial class SettingsShadersPatchPopup : ILocalKeyBindings, IContentLoader { + public SettingsShadersPatchPopup() { + KeyBindingsController = new LocalKeyBindingsController(this); + /*InputBindings.Add(new InputBinding(new DelegateCommand(() => { + Model.SelectedApp?.ViewInExplorerCommand.Execute(null); + }), new KeyGesture(Key.F, ModifierKeys.Control))); + InputBindings.Add(new InputBinding(new DelegateCommand(() => { + Model.SelectedApp?.ReloadCommand.Execute(null); + }), new KeyGesture(Key.R, ModifierKeys.Control)));*/ + + InitializeComponent(); + DataContext = new SettingsShadersPatch.ViewModel(true); + Model.PropertyChanged += OnModelPropertyChanged; + SetKeyboardInputs(); + UpdateConfigsTabs(); + Tabs.ContentLoader = this; + this.OnActualUnload(() => { Model?.Dispose(); }); + } + + private void OnModelPropertyChanged(object sender, PropertyChangedEventArgs e) { + if (e.PropertyName == nameof(Model.Configs)) { + UpdateConfigsTabs(); + } + + if (e.PropertyName == nameof(Model.SelectedConfig)) { + //SetKeyboardInputs(); + //UpdateConfigsTabs(); + } + } + + public Task LoadContentAsync(Uri uri, CancellationToken cancellationToken) { + return Task.FromResult(LoadContent(uri)); + } + + public object LoadContent(Uri uri) { + var config = Model.Configs?.FirstOrDefault(x => x.Id == uri.OriginalString); + return new ContentControl { + ContentTemplate = (DataTemplate)FindResource("PythonAppConfig.Compact.NoHeader"), + Content = config + }; + } + + private void UpdateConfigsTabs() { + try { + var links = Tabs.Links; + links.Clear(); + Tabs.SelectedSource = null; + + if (Model.Configs?.Count > 1) { + foreach (var config in Model.Configs) { + links.Add(new Link { + DisplayName = config.DisplayName, + Key = config.Id + }); + } + Tabs.LinksMargin = new Thickness(0, 0, 0, 4); + Tabs.SelectedSource = Tabs.Links.FirstOrDefault()?.Source; + } else { + Tabs.LinksMargin = new Thickness(0, 0, 0, -16); + Tabs.SelectedSource = Model.Configs == null ? null + : new Uri(Model.Configs.First().Id, UriKind.Relative); + } + } catch (Exception e) { + Logging.Error(e); + } + } + + public LocalKeyBindingsController KeyBindingsController { get; } + + private void SetKeyboardInputs() { + KeyBindingsController.Set(Model.SelectedConfig?.Sections.SelectMany().OfType()); + } + + private SettingsShadersPatch.ViewModel Model => (SettingsShadersPatch.ViewModel)DataContext; + + protected override void OnKeyDown(KeyEventArgs e) { + if (e.Key == Key.Tab) { + var selected = Tabs.SelectedSource; + Tabs.SelectedSource = ( + User32.IsKeyPressed(Keys.LShiftKey) || User32.IsKeyPressed(Keys.RShiftKey) ? + Tabs.Links.Concat(Tabs.Links.TakeWhile(x => x.Source != selected)).LastOrDefault() : + Tabs.Links.SkipWhile(x => x.Source != selected).Skip(1).Concat(Tabs.Links).FirstOrDefault() + )?.Source ?? selected; + e.Handled = true; + } else if (e.Key >= Key.D1 && e.Key <= Key.D9 && + (User32.IsKeyPressed(Keys.LControlKey) || User32.IsKeyPressed(Keys.RControlKey))) { + Tabs.SelectedSource = Tabs.Links.ElementAtOrDefault(e.Key - Key.D1)?.Source ?? Tabs.SelectedSource; + e.Handled = true; + } + } + + private bool _selectionSet; + + [CanBeNull] + private Cell _selectionCell; + + [CanBeNull] + private ListBox _selectionListBox; + + private ScaleTransform _selectionScaleTransform; + private TranslateTransform _selectionTranslateTransform; + private EasingFunctionBase _selectionEasingFunction; + + private void InitializeMovingSelectionHighlight() { + _selectionSet = true; + + _selectionCell = Tabs.FindVisualChildren().FirstOrDefault(x => x.Name == "PART_Cell"); + _selectionListBox = _selectionCell?.FindVisualChildren().FirstOrDefault(x => x.Name == "PART_LinkList"); + if (_selectionCell == null || _selectionListBox == null) return; + + SetSelected(); + } + + private void MoveInitializedSelectionHighlight() { + SetSelected(); + } + + [CanBeNull] + private Tuple GetSelected() { + if (_selectionCell == null || _selectionListBox == null) { + return null; + } + + var selected = (ListBoxItem)_selectionListBox.GetItemVisual(_selectionListBox.SelectedItem); + return selected == null ? null : Tuple.Create(selected.TransformToAncestor(_selectionCell).Transform(new Point(0, 0)), + new Size(selected.ActualWidth / Math.Max(_selectionCell.ActualWidth, 1d), selected.ActualHeight / Math.Max(_selectionCell.ActualHeight, 1d))); + } + + private void SetSelected() { + var selected = GetSelected(); + if (selected == null || _selectionCell == null) return; + + if (_selectionScaleTransform == null) { + _selectionScaleTransform = new ScaleTransform { ScaleX = selected.Item2.Width, ScaleY = selected.Item2.Height }; + _selectionTranslateTransform = new TranslateTransform { X = selected.Item1.X, Y = selected.Item1.Y }; + + var box = _selectionCell.FindVisualChildren().FirstOrDefault(x => x.Name == "PART_SelectionBox"); + if (box != null) { + box.RenderTransform = new TransformGroup { Children = { _selectionScaleTransform, _selectionTranslateTransform } }; + } + } else { + var duration = TimeSpan.FromSeconds(0.2 + (((_selectionTranslateTransform.X - selected.Item1.X).Abs() - 100d) / 500d).Clamp(0d, 0.5d)); + var easing = _selectionEasingFunction ?? (_selectionEasingFunction = (EasingFunctionBase)FindResource("StandardEase")); + _selectionTranslateTransform.BeginAnimation(TranslateTransform.XProperty, + new DoubleAnimation { To = selected.Item1.X, Duration = duration, EasingFunction = easing }); + _selectionTranslateTransform.BeginAnimation(TranslateTransform.YProperty, + new DoubleAnimation { To = selected.Item1.Y, Duration = duration, EasingFunction = easing }); + _selectionScaleTransform.BeginAnimation(ScaleTransform.ScaleXProperty, + new DoubleAnimation { To = selected.Item2.Width, Duration = duration, EasingFunction = easing }); + _selectionScaleTransform.BeginAnimation(ScaleTransform.ScaleYProperty, + new DoubleAnimation { To = selected.Item2.Height, Duration = duration, EasingFunction = easing }); + } + } + + private void MoveSelectionHighlight() { + try { + if (_selectionSet) { + MoveInitializedSelectionHighlight(); + } else { + InitializeMovingSelectionHighlight(); + } + } catch (Exception e) { + Logging.Error(e); + } + } + + private void OnLoaded(object sender, RoutedEventArgs e) { + MoveSelectionHighlight(); + } + + private void OnSelectedSourceChanged(object sender, SourceEventArgs e) { + if (_selectionTranslateTransform == null) return; + MoveSelectionHighlight(); + } + } +} \ No newline at end of file diff --git a/AcManager/Pages/Windows/MainWindow.xaml b/AcManager/Pages/Windows/MainWindow.xaml index 5dea4f0f5..d2d214e88 100644 --- a/AcManager/Pages/Windows/MainWindow.xaml +++ b/AcManager/Pages/Windows/MainWindow.xaml @@ -404,7 +404,7 @@ Source="/Pages/Drive/Online.xaml" FilterHint="{x:Static c:FilterHints.ServerEntries}"> - + @@ -481,6 +481,7 @@ + diff --git a/AcManager/Pages/Windows/MainWindow.xaml.cs b/AcManager/Pages/Windows/MainWindow.xaml.cs index 20334cdee..d546b0119 100644 --- a/AcManager/Pages/Windows/MainWindow.xaml.cs +++ b/AcManager/Pages/Windows/MainWindow.xaml.cs @@ -209,6 +209,8 @@ protected override void OnLoadedOverride() { this.FindVisualChild()?.SkipLoading(); _navigateOnOpen = null; } + + // SettingsShadersPatch.GetShowSettingsCommand().Execute(null); } private readonly Busy _openDownloadsListBusy = new Busy(); @@ -310,7 +312,7 @@ private void OnTitleLinkEnabledChanged(object o, PropertyChangedEventArgs args) } private void UpdateMinoratingLink() { - MinoratingLink.IsShown = SettingsHolder.Online.IntegrateMinorating; + // MinoratingLink.IsShown = SettingsHolder.Online.IntegrateMinorating; } private void OnLiveSettingsPropertyChanged(object sender, PropertyChangedEventArgs e) { diff --git a/AcTools.Render/AcTools.Render.csproj b/AcTools.Render/AcTools.Render.csproj index 573e0e2b2..9630bf68d 100644 --- a/AcTools.Render/AcTools.Render.csproj +++ b/AcTools.Render/AcTools.Render.csproj @@ -266,7 +266,6 @@ - diff --git a/AcTools.Render/Base/Sprites/TextBlockRenderer.cs b/AcTools.Render/Base/Sprites/TextBlockRenderer.cs index 135670b9c..f97169186 100644 --- a/AcTools.Render/Base/Sprites/TextBlockRenderer.cs +++ b/AcTools.Render/Base/Sprites/TextBlockRenderer.cs @@ -20,10 +20,6 @@ using Resource = SlimDX.DXGI.Resource; using ShaderResourceView = SlimDX.Direct3D11.ShaderResourceView; -#if DEBUG -using Debug = System.Diagnostics.Debug; -#endif - namespace AcTools.Render.Base.Sprites { /// /// Defines how a text is aligned in a rectangle. Use OR-combinations of vertical and horizontal alignment. @@ -130,7 +126,6 @@ static TextBlockRenderer() { private readonly Dictionary _charTables = new Dictionary(); private readonly RenderTargetProperties _rtp; - private readonly IFontCollectionProvider _fcp; public TextBlockRenderer(SpriteRenderer sprite, string fontName, FontWeight fontWeight, FontStyle fontStyle, FontStretch fontStretch, float fontSize, int kerningAdjustment = 0) @@ -155,8 +150,10 @@ public TextBlockRenderer(SpriteRenderer sprite, [CanBeNull] IFontCollectionProvi }; if (collection != null) { - _fcp = collection; var c = collection.GetCollection(WriteFactory); + if (c.FindFamilyName(fontName) == -1) { + fontName = c[0].FamilyNames.get_String(0); + } Font = WriteFactory.CreateTextFormat(fontName, c, fontWeight, fontStyle, fontStretch, fontSize, CultureInfo.CurrentCulture.Name); } else { @@ -280,13 +277,6 @@ private void CreateCharTable(byte bytePrefix) { foreach (var layout in tl) { layout.Dispose(); } -#if DEBUG - Debug.WriteLine("Created Char Table " + bytePrefix + " in " + sizeX + " x " + sizeY); -#endif - - // System.Threading.Monitor.Enter(D3DDevice11); - // SlimDX.Direct3D11.Texture2D.SaveTextureToFile(Sprite.Device.ImmediateContext, Texture11, SlimDX.Direct3D11.ImageFileFormat.Png, Font.FontFamilyName + "Table" + BytePrefix + ".png"); - // System.Threading.Monitor.Exit(D3DDevice11); _charTables.Add(bytePrefix, tableDesc); } @@ -409,27 +399,37 @@ public StringMetrics DrawString(string text, Vector2 position, float angle, Text // TODO: support for multiline strings plus angle public StringMetrics DrawString(string text, RectangleF rect, float angle, TextAlignment align, float realFontSize, Color4 color, CoordinateType coordinateType) { - if (align.HasFlag(TextAlignment.Top) && align.HasFlag(TextAlignment.Left)) { + var hl = align.HasFlag(TextAlignment.Left); + var vt = align.HasFlag(TextAlignment.Top); + if (hl && vt) { return DrawString(text, new Vector2(rect.X, rect.Y), angle, realFontSize, color, coordinateType); } - var m = MeasureString(text, angle, realFontSize, coordinateType); - var y = align.HasFlag(TextAlignment.Top) ? rect.Top : - align.HasFlag(TextAlignment.VerticalCenter) ? rect.Top + rect.Height / 2 - m.Size.Y / 2 : - rect.Bottom - m.Size.Y; + var hc = align.HasFlag(TextAlignment.HorizontalCenter); + var vc = align.HasFlag(TextAlignment.VerticalCenter); + + var m = MeasureString(angle == 0f ? text : "00", angle, realFontSize, coordinateType); var p = new Vector2( - align.HasFlag(TextAlignment.Left) ? rect.X : - align.HasFlag(TextAlignment.HorizontalCenter) ? - rect.X + rect.Width / 2 - m.Size.X / 2 : rect.X + rect.Width - m.Size.X, - y); + hl ? rect.X : hc ? rect.X + rect.Width / 2 - m.Size.X / 2 : rect.Right - m.Size.X, + vt ? rect.Y : vc ? rect.Y + rect.Height / 2 - m.Size.Y / 2 : rect.Bottom - m.Size.Y); if (angle != 0f) { var o = m.Size / 2f; o.Y /= 2f; p += o - SpriteRenderer.Rotate(o, (float)Math.Sin(angle), (float)Math.Cos(angle)); + p -= SpriteRenderer.Rotate( + GetOffset(MeasureString(text, 0f, realFontSize, coordinateType)) + - GetOffset(MeasureString("00", 0f, realFontSize, coordinateType)), + (float)Math.Sin(angle), (float)Math.Cos(angle)); } return DrawString(text, p, angle, realFontSize, color, coordinateType); + + Vector2 GetOffset(StringMetrics stringMetrics) { + var xm = hl ? 0f : hc ? stringMetrics.Size.X / 2 : stringMetrics.Size.X; + var ym = vt ? 0f : vc ? stringMetrics.Size.Y / 2 : stringMetrics.Size.Y; + return new Vector2(xm, ym); + } } public StringMetrics DrawString(string text, RectangleF rect, float angle, TextAlignment align, Color4 color) { diff --git a/AcTools.Render/Kn5Specific/Animations/KsAnimAnimator.cs b/AcTools.Render/Kn5Specific/Animations/KsAnimAnimator.cs index 116af5724..a8f8a51fb 100644 --- a/AcTools.Render/Kn5Specific/Animations/KsAnimAnimator.cs +++ b/AcTools.Render/Kn5Specific/Animations/KsAnimAnimator.cs @@ -7,7 +7,6 @@ using AcTools.Render.Base.Objects; using AcTools.Render.Base.Utils; using AcTools.Render.Kn5Specific.Objects; -using AcTools.Render.Kn5Specific.Textures; using AcTools.Utils; using AcTools.Utils.Helpers; using JetBrains.Annotations; @@ -78,7 +77,7 @@ public KsAnimAnimator(string filename, float duration, bool skipFixed) { _skipFixed = skipFixed; _filename = filename; _original = File.Exists(_filename) ? KsAnim.FromFile(_filename) : KsAnim.CreateEmpty(); - _watcher = DirectoryWatcher.WatchFile(filename, Reload); + _watcher = SimpleDirectoryWatcher.WatchFile(filename, Reload); } private void Reload() { diff --git a/AcTools.Render/Kn5Specific/Objects/Kn5RenderableCar.DriverCrew.cs b/AcTools.Render/Kn5Specific/Objects/Kn5RenderableCar.DriverCrew.cs index 64519e860..25f6545d9 100644 --- a/AcTools.Render/Kn5Specific/Objects/Kn5RenderableCar.DriverCrew.cs +++ b/AcTools.Render/Kn5Specific/Objects/Kn5RenderableCar.DriverCrew.cs @@ -8,7 +8,6 @@ using AcTools.Render.Base.Objects; using AcTools.Render.Base.Utils; using AcTools.Render.Kn5Specific.Animations; -using AcTools.Render.Kn5Specific.Textures; using AcTools.Utils; using AcTools.Utils.Helpers; using JetBrains.Annotations; @@ -115,7 +114,7 @@ private void InitializeDriver() { if (_driverHierarchyFilename == null) { _driverHierarchyFilename = Path.Combine(_rootDirectory, "driver_base_pos.knh"); - _driverHierarchyWatcher = DirectoryWatcher.WatchFile(_driverHierarchyFilename, () => { + _driverHierarchyWatcher = SimpleDirectoryWatcher.WatchFile(_driverHierarchyFilename, () => { _driver?.AlignNodes(Knh.FromFile(_driverHierarchyFilename)); }); } @@ -128,7 +127,7 @@ private void InitializeDriver() { var driversDirectory = Path.Combine(contentDirectory, "driver"); _driverModelFilename = Path.Combine(driversDirectory, driver.Name + ".kn5"); - _driverModelWatcher = DirectoryWatcher.WatchFile(_driverModelFilename, ReloadDriverModel); + _driverModelWatcher = SimpleDirectoryWatcher.WatchFile(_driverModelFilename, ReloadDriverModel); LoadDriverModel(); ObjectsChanged?.Invoke(this, EventArgs.Empty); diff --git a/AcTools.Render/Kn5Specific/Textures/Kn5OverrideableTexturesProvider.cs b/AcTools.Render/Kn5Specific/Textures/Kn5OverrideableTexturesProvider.cs index 86d6850eb..2ea0424d1 100644 --- a/AcTools.Render/Kn5Specific/Textures/Kn5OverrideableTexturesProvider.cs +++ b/AcTools.Render/Kn5Specific/Textures/Kn5OverrideableTexturesProvider.cs @@ -91,7 +91,7 @@ public override void SetOverridesDirectory(IDeviceContextHolder holder, string d ClearOverridesDirectory(); ContentTexturesDirectory = GetContentTexturesDirectory(directory); - _contentTexturesWatching = DirectoryWatcher.WatchDirectory(ContentTexturesDirectory, filename => { + _contentTexturesWatching = SimpleDirectoryWatcher.WatchDirectory(ContentTexturesDirectory, filename => { if (CurrentDirectory != null) { UpdateOverrideLater(Path.Combine(CurrentDirectory, "skin.ini")); } @@ -182,7 +182,7 @@ public virtual void ClearOverridesDirectory() { protected void SetOverridesDirectoryInner([NotNull] IDeviceContextHolder holder, [NotNull] string directory) { _holder = holder; CurrentDirectory = directory; - _watching = DirectoryWatcher.WatchDirectory(directory, filename => { + _watching = SimpleDirectoryWatcher.WatchDirectory(directory, filename => { if (filename == null) { UpdateOverridesLater(); } else { diff --git a/AcTools.Render/Kn5SpecificForward/ToolsKn5ObjectRenderer.Paintshop.cs b/AcTools.Render/Kn5SpecificForward/ToolsKn5ObjectRenderer.Paintshop.cs index becb17a32..589904e11 100644 --- a/AcTools.Render/Kn5SpecificForward/ToolsKn5ObjectRenderer.Paintshop.cs +++ b/AcTools.Render/Kn5SpecificForward/ToolsKn5ObjectRenderer.Paintshop.cs @@ -538,7 +538,7 @@ private void InitializePatternTextRenderer() { } } - private Dictionary _patternFontsCollections = new Dictionary(); + private static Dictionary _patternFontsCollections = new Dictionary(); private class FontCollectionProvider : IFontCollectionProvider { private readonly PaintShopFontSource _source; @@ -550,8 +550,7 @@ public FontCollectionProvider(PaintShopFontSource source) { public FontCollection GetCollection(Factory factory) { if (_collection != null) return _collection; - _collection = factory.CreateCustomFontCollection(_source.Filename); - return _collection; + return _collection = factory.CreateCustomFontCollection(_source.Filename); } public void Dispose() { @@ -1010,7 +1009,7 @@ public void DisposePaintShop() { } _paintShopFlags?.DisposeEverything(); - _patternFontsCollections?.DisposeEverything(); + // _patternFontsCollections?.DisposeEverything(); // _override?.DisposeEverything(); /*_patternBase?.DisposeEverything(); diff --git a/AcTools.Tests/AcTools.Tests.csproj b/AcTools.Tests/AcTools.Tests.csproj index 054e44511..59c737c01 100644 --- a/AcTools.Tests/AcTools.Tests.csproj +++ b/AcTools.Tests/AcTools.Tests.csproj @@ -58,6 +58,9 @@ + + + diff --git a/AcTools.Tests/LutTest.cs b/AcTools.Tests/LutTest.cs index 6b510c146..59cfc335e 100644 --- a/AcTools.Tests/LutTest.cs +++ b/AcTools.Tests/LutTest.cs @@ -1,4 +1,9 @@ +using System; +using System.Diagnostics; +using System.Drawing; +using System.IO; using System.Linq; +using System.Windows.Forms.DataVisualization.Charting; using AcTools.Utils.Physics; using NUnit.Framework; @@ -52,6 +57,74 @@ public void CubicInterpolation() { Assert.IsTrue(lut.InterpolateCubic(1.9d) < 1d); } + private void BuildChart(params Action[] series) { + using (var ch = new Chart()) { + ch.Size = new Size(1024, 1024); + ch.ChartAreas.Add(new ChartArea()); + + foreach (var se in series) { + var s = new Series { ChartType = SeriesChartType.Line }; + se(s); + ch.Series.Add(s); + } + + var filename = Path.Combine(Path.GetTempPath(), "at_test.png"); + ch.SaveImage(filename, ChartImageFormat.Png); + Process.Start(filename); + } + } + + [Test] + public void CubicInterpolationExtra() { + var lut = new Lut { + new LutPoint(-0.009994, 1.059), + new LutPoint(-0.004998, 1.059), + new LutPoint(0, 1.060), + new LutPoint(0.005002, 1.063), + new LutPoint(0.010007, 1.065), + new LutPoint(0.015014, 1.068), + new LutPoint(0.020024, 1.073), + new LutPoint(0.025039, 1.078), + new LutPoint(0.030057, 1.083), + new LutPoint(0.03508, 1.089), + new LutPoint(0.040106, 1.095), + }; + + lut.UpdateBoundingBox(); + + var minX = lut.MinX; + var sizeX = lut.MaxX - lut.MinX; + var minY = lut.MinY; + var sizeY = lut.MaxY - lut.MinY; + lut = new Lut(lut.Select(x => new LutPoint((x.X - minX) / sizeX, (x.Y - minY) / sizeY))); + lut.UpdateBoundingBox(); + + BuildChart(s => { + var min = lut.MinX; + var size = lut.MaxX - lut.MinX; + + for (var i = 0d; i <= 1d; i += 0.003) { + var x = lut.MinX + size * i; + s.Points.Add(new DataPoint(x, lut.InterpolateLinear(x))); + } + + s.Color = Color.Black; + }, s => { + var min = lut.MinX; + var size = lut.MaxX - lut.MinX; + + for (var i = 0d; i <= 1d; i += 0.003) { + var x = lut.MinX + size * i; + s.Points.Add(new DataPoint(x, lut.InterpolateCubic(x))); + } + + s.Color = Color.Green; + }); + + // interpolation itself + Assert.IsTrue(lut.InterpolateCubic(0.003) > lut.InterpolateCubic(0.001)); + } + [Test] public void LutValueParse() { var lut = Lut.FromValue("(|0=0.7|1000=0.8|4000=0.9|)"); diff --git a/AcTools/AcTools.csproj b/AcTools/AcTools.csproj index 25601ba57..1b1215313 100644 --- a/AcTools/AcTools.csproj +++ b/AcTools/AcTools.csproj @@ -241,6 +241,7 @@ + diff --git a/AcTools/DataFile/IniFile.cs b/AcTools/DataFile/IniFile.cs index e1295e229..cafd9d305 100644 --- a/AcTools/DataFile/IniFile.cs +++ b/AcTools/DataFile/IniFile.cs @@ -140,7 +140,7 @@ protected override void ParseString([NotNull] string data) { break; case '/': - if (i + 1 < data.Length && data[i + 1] == '/') { + if (i + 1 < data.Length && data[i + 1] == '/' && (i == 0 || data[i - 1] != ':')) { goto case ';'; } goto default; @@ -273,7 +273,7 @@ private void ParseStringWithComments(string data) { break; case '/': - if (i + 1 < data.Length && data[i + 1] == '/') { + if (i + 1 < data.Length && data[i + 1] == '/' && (i == 0 || data[i - 1] != ':')) { i++; goto case ';'; } @@ -293,9 +293,11 @@ private void ParseStringWithComments(string data) { var commentLength = i - commentStartIndex; if (currentSection != null && commentLength > 0) { if (previousKey != null) { - currentSection.SetCommentary(previousKey, data.Substring(commentStartIndex, commentLength)); + currentSection.SetCommentary(previousKey, + (currentSection.Commentaries?.GetValueOrDefault(previousKey) + "\n" + + data.Substring(commentStartIndex, commentLength)).Trim()); } else if (currentSection.Count == 0) { - currentSection.SetCommentary(data.Substring(commentStartIndex, commentLength)); + currentSection.SetCommentary((currentSection.Commentary + "\n" + data.Substring(commentStartIndex, commentLength)).Trim()); } } break; @@ -508,7 +510,7 @@ private static string SetValue(string data, string section, string key, string v break; case '/': - if (i + 1 < data.Length && data[i + 1] == '/') { + if (i + 1 < data.Length && data[i + 1] == '/' && (i == 0 || data[i - 1] != ':')) { goto case ';'; } goto default; @@ -567,7 +569,7 @@ private static string RemoveSection(string data, string section) { current = data.Substring(started + 1, i - started - 1) == section; break; case '/': - if (i + 1 < data.Length && data[i + 1] == '/') { + if (i + 1 < data.Length && data[i + 1] == '/' && (i == 0 || data[i - 1] != ':')) { goto case ';'; } break; diff --git a/AcTools/Utils/Physics/Lut.cs b/AcTools/Utils/Physics/Lut.cs index 675b4b8a0..ab7be835d 100644 --- a/AcTools/Utils/Physics/Lut.cs +++ b/AcTools/Utils/Physics/Lut.cs @@ -154,11 +154,13 @@ private LutPoint GetClamped(int i) { } private double GetTangent(int k) { - return GetTangent(GetClamped(k - 1), GetClamped(k + 1)); + var kc = GetClamped(k); + return (GetTangent(GetClamped(k - 1), kc) + GetTangent(kc, GetClamped(k + 1))) / 2d; } private double GetTangent(LutPoint p, LutPoint n) { - return (n.Y - p.Y) / Math.Abs(n.X - p.X); + if (n.X == p.X) return 0d; + return (n.Y - p.Y) / (n.X - p.X); } [Pure] @@ -173,11 +175,13 @@ public double InterpolateCubic(double x) { var k = FindLeft(x); var p1 = GetClamped(k); var p2 = GetClamped(k + 1); - var t1 = (x - p1.X) / (p2.X - p1.X); + var di = (p2.X - p1.X); + var t1 = (x - p1.X) / di; var t2 = t1 * t1; var t3 = t1 * t2; - return (2 * t3 - 3 * t2 + 1) * p1.Y + (t3 - 2 * t2 + t1) * GetTangent(k) + - (-2 * t3 + 3 * t2) * p2.Y + (t3 - t2) * GetTangent(k + 1); + + return (2 * t3 - 3 * t2 + 1) * p1.Y + (t3 - 2 * t2 + t1) * GetTangent(k) * di + + (-2 * t3 + 3 * t2) * p2.Y + (t3 - t2) * GetTangent(k + 1) * di; } [Pure, NotNull] diff --git a/AcTools.Render/Kn5Specific/Textures/DirectoryWatcher.cs b/AcTools/Utils/SimpleDirectoryWatcher.cs similarity index 96% rename from AcTools.Render/Kn5Specific/Textures/DirectoryWatcher.cs rename to AcTools/Utils/SimpleDirectoryWatcher.cs index e4d441d61..f42c472b6 100644 --- a/AcTools.Render/Kn5Specific/Textures/DirectoryWatcher.cs +++ b/AcTools/Utils/SimpleDirectoryWatcher.cs @@ -3,13 +3,12 @@ using System.IO; using System.Linq; using System.Threading.Tasks; -using AcTools.Utils; using JetBrains.Annotations; -namespace AcTools.Render.Kn5Specific.Textures { - internal delegate void DirectoryChanged([CanBeNull] string filename); +namespace AcTools.Utils { + public delegate void DirectoryChanged([CanBeNull] string filename); - internal static class DirectoryWatcher { + public static class SimpleDirectoryWatcher { private class Entry { public FileSystemWatcher Watcher; public int Count; diff --git a/CustomTracksBakery/Properties/AssemblyInfo.cs b/CustomTracksBakery/Properties/AssemblyInfo.cs index 11e7af174..39bdb6da2 100644 --- a/CustomTracksBakery/Properties/AssemblyInfo.cs +++ b/CustomTracksBakery/Properties/AssemblyInfo.cs @@ -35,6 +35,6 @@ // by using the '*' as shown below: // [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("1.0.0.702")] -[assembly: AssemblyFileVersion("1.0.0.702")] -// Modified at: 18/10/02 00:55:26 \ No newline at end of file +[assembly: AssemblyVersion("1.0.0.703")] +[assembly: AssemblyFileVersion("1.0.0.703")] +// Modified at: 18/10/16 18:33:10 \ No newline at end of file diff --git a/FirstFloor.ModernUI/Helpers/ContextMenuExtension.cs b/FirstFloor.ModernUI/Helpers/ContextMenuExtension.cs index d7b7371fc..4f55c255b 100644 --- a/FirstFloor.ModernUI/Helpers/ContextMenuExtension.cs +++ b/FirstFloor.ModernUI/Helpers/ContextMenuExtension.cs @@ -47,19 +47,21 @@ public static ContextMenu AddItem([NotNull] this ContextMenu menu, string header return menu; } - public static ContextMenu AddItem([NotNull] this ContextMenu menu, string header, Action action, string shortcut = null, object icon = null, + public static ContextMenu AddItem([NotNull] this ContextMenu menu, [NotNull] string header, Action action, string shortcut = null, object icon = null, Geometry iconData = null, bool isEnabled = true) { if (menu == null) throw new ArgumentNullException(nameof(menu)); return menu.AddItem(header, new DelegateCommand(action), shortcut, icon, iconData, isEnabled); } - public static ContextMenu AddItem([NotNull] this ContextMenu menu, ICommand command) { + public static ContextMenu AddItem([NotNull] this ContextMenu menu, [CanBeNull] ICommand command) { + if (command == null) return menu; if (menu == null) throw new ArgumentNullException(nameof(menu)); menu.Items.Add(new MenuItem { Command = command }); return menu; } - public static ContextMenu AddItem([NotNull] this ContextMenu menu, MenuItem item) { + public static ContextMenu AddItem([NotNull] this ContextMenu menu, [CanBeNull] MenuItem item) { + if (item == null) return menu; if (menu == null) throw new ArgumentNullException(nameof(menu)); menu.Items.Add(item); return menu; @@ -67,6 +69,7 @@ public static ContextMenu AddItem([NotNull] this ContextMenu menu, MenuItem item public static ContextMenu AddSeparator([NotNull] this ContextMenu menu) { if (menu == null) throw new ArgumentNullException(nameof(menu)); + if (menu.Items.Count == 0) return menu; menu.Items.Add(new Separator()); return menu; } diff --git a/FirstFloor.ModernUI/UiStrings.Designer.cs b/FirstFloor.ModernUI/UiStrings.Designer.cs index 9c2b72716..7f57f455e 100644 --- a/FirstFloor.ModernUI/UiStrings.Designer.cs +++ b/FirstFloor.ModernUI/UiStrings.Designer.cs @@ -24,7 +24,7 @@ namespace FirstFloor.ModernUI { [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] public class UiStrings { - private static global::System.Resources.ResourceManager resourceMan; + private static FirstFloor.ModernUI.CustomResourceManager resourceMan; private static global::System.Globalization.CultureInfo resourceCulture; @@ -36,10 +36,10 @@ internal UiStrings() { /// Returns the cached ResourceManager instance used by this class. /// [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] - public static global::System.Resources.ResourceManager ResourceManager { + public static FirstFloor.ModernUI.CustomResourceManager ResourceManager { get { if (object.ReferenceEquals(resourceMan, null)) { - global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("FirstFloor.ModernUI.UiStrings", typeof(UiStrings).Assembly); + FirstFloor.ModernUI.CustomResourceManager temp = new FirstFloor.ModernUI.CustomResourceManager("FirstFloor.ModernUI.UiStrings", typeof(UiStrings).Assembly); resourceMan = temp; } return resourceMan; diff --git a/FirstFloor.ModernUI/Windows/Controls/HierarchicalComboBox.cs b/FirstFloor.ModernUI/Windows/Controls/HierarchicalComboBox.cs index efd80c896..ff01c5478 100644 --- a/FirstFloor.ModernUI/Windows/Controls/HierarchicalComboBox.cs +++ b/FirstFloor.ModernUI/Windows/Controls/HierarchicalComboBox.cs @@ -9,7 +9,6 @@ using System.Windows.Controls; using System.Windows.Data; using System.Windows.Input; -using FirstFloor.ModernUI.Helpers; using FirstFloor.ModernUI.Presentation; using FirstFloor.ModernUI.Serialization; using JetBrains.Annotations; diff --git a/Libraries/AcManager.Internal/AcManager.Internal.dll b/Libraries/AcManager.Internal/AcManager.Internal.dll index fa63168de..4b579cdad 100644 Binary files a/Libraries/AcManager.Internal/AcManager.Internal.dll and b/Libraries/AcManager.Internal/AcManager.Internal.dll differ diff --git a/Libraries/SlimDX-x86/SlimDX.dll b/Libraries/SlimDX-x86/SlimDX.dll index 044013965..54942c31a 100644 Binary files a/Libraries/SlimDX-x86/SlimDX.dll and b/Libraries/SlimDX-x86/SlimDX.dll differ