Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 7 additions & 30 deletions src/ui/Features/Ocr/OcrViewModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -159,7 +159,7 @@ public partial class OcrViewModel : ObservableObject
[ObservableProperty] string _unknownWordsRemoveCurrentText;

public Window? Window { get; set; }
public DataGrid SubtitleGrid { get; set; }
public TableView SubtitleGrid { get; set; }

// Rebuild the combo row visuals after a download finishes so the install-status dots
// reflect the current on-disk state instead of the snapshot taken when first realised.
Expand Down Expand Up @@ -230,7 +230,7 @@ public OcrViewModel(
.Select(p => p.EnglishName)
.OrderBy(p => p));
SelectedOllamaLanguage = "English";
SubtitleGrid = new DataGrid();
SubtitleGrid = new TableView();
CurrentBitmapInfo = string.Empty;
CurrentText = string.Empty;
ProgressText = string.Empty;
Expand Down Expand Up @@ -4363,10 +4363,10 @@ internal void OnKeyDown(KeyEventArgs e)
e.Handled = true;
if (ImageMaxHeight < 300)
{
// TableView rows auto-size to their content, so changing the bound
// Image Max sizes re-measures the rows - no explicit RowHeight needed.
ImageMaxHeight *= 1.1;
ImageMaxWidth *= 1.1;
var rowHeight = CalculateRowHeight();
Dispatcher.UIThread.Post(() => SubtitleGrid.RowHeight = rowHeight);
}
}
else if ((e.Key == Key.Subtract || e.Key == Key.OemMinus) && e.KeyModifiers.HasFlag(KeyModifiers.Control))
Expand All @@ -4376,8 +4376,6 @@ internal void OnKeyDown(KeyEventArgs e)
{
ImageMaxHeight *= 0.9;
ImageMaxWidth *= 0.9;
var rowHeight = CalculateRowHeight();
Dispatcher.UIThread.Post(() => SubtitleGrid.RowHeight = rowHeight);
}
}
else if (e.Key == Key.V && e.KeyModifiers.HasFlag(KeyModifiers.Control) && e.KeyModifiers.HasFlag(KeyModifiers.Shift))
Expand Down Expand Up @@ -4446,10 +4444,9 @@ await Dispatcher.UIThread.InvokeAsync(() =>
SubtitleGrid.SelectedIndex = indexToScroll;

// Post ScrollIntoView as a second background task so it runs after
// the DataGrid has processed the selection change and updated its layout.
var item = OcrSubtitleItems[indexToScroll];
// the grid has processed the selection change and updated its layout.
Dispatcher.UIThread.Post(
() => SubtitleGrid.ScrollIntoView(item, null),
() => SubtitleGrid.ScrollIntoView(indexToScroll),
DispatcherPriority.Background);
}
}, DispatcherPriority.Background);
Expand Down Expand Up @@ -4585,26 +4582,6 @@ internal void InitializeDivX(List<XSub> list, string fileName)
SetOcrSubtitleItems();
}

private double CalculateRowHeight()
{
var firstItem = OcrSubtitleItems.FirstOrDefault();
if (firstItem == null)
{
return ImageMaxHeight + 10;
}

var bitmap = firstItem.GetSkBitmap();
var natW = (double)bitmap.Width;
var natH = (double)bitmap.Height;
if (natW <= 0 || natH <= 0)
{
return ImageMaxHeight + 10;
}

var scale = Math.Min(ImageMaxWidth / natW, ImageMaxHeight / natH);
return natH * scale + 10;
}

internal void EngineSelectionChanged(object? sender, SelectionChangedEventArgs e)
{
EngineSelectionChanged();
Expand Down Expand Up @@ -4875,7 +4852,7 @@ internal void OnLoaded()
{
SelectedOcrSubtitleItem = OcrSubtitleItems[0];
SubtitleGrid.SelectedIndex = 0;
SubtitleGrid.ScrollIntoView(SelectedOcrSubtitleItem, null);
SubtitleGrid.ScrollIntoView(0);
TrackChanged();
}
});
Expand Down
126 changes: 87 additions & 39 deletions src/ui/Features/Ocr/OcrWindow.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
using Avalonia.Data.Converters;
using Avalonia.Input;
using Avalonia.Layout;
using Avalonia.Media;
using Avalonia.Styling;
using Nikse.SubtitleEdit.Features.Ocr.Engines;
using Nikse.SubtitleEdit.Features.Ocr.FixEngine;
Expand All @@ -18,6 +19,8 @@
using System;
using System.Collections;
using System.ComponentModel;
using System.Globalization;
using System.Linq;
using Nikse.SubtitleEdit.UiLogic.Ocr.FixEngine;
using MenuItem = Avalonia.Controls.MenuItem;
using Nikse.SubtitleEdit.UiLogic.Ocr;
Expand Down Expand Up @@ -405,61 +408,95 @@ private static Border MakeSubtitleView(OcrViewModel vm)
{
var fullTimeConverter = new TimeSpanToDisplayFullConverter();
var shortTimeConverter = new TimeSpanToDisplayShortConverter();
var dataGridSubtitle = new DataGrid

// TableView (Avalonia 12.1) pilot #2, after Show history (#12704): this is SE's
// heaviest grid - virtualized template columns with per-row bitmaps - chosen to
// judge TableView's scrolling against DataGrid's. Differences vs the DataGrid it
// replaces: no column sorting (TableView has none), and extended selection
// (shift/ctrl-click, shift+arrows) is native ListBox behavior instead of
// DataGridCheckboxMultiSelect.
//
// TableView has no content-based column sizing either - GridLength.Auto is
// treated as 1* by its layout helper - so the narrow columns get pixel widths
// measured from their widest content (the VM is initialized before the window
// ctor, so the items are available here).
const double cellChrome = 16; // cell padding/margins + slack
double MeasureWidth(string text) =>
new FormattedText(text, CultureInfo.CurrentUICulture, FlowDirection.LeftToRight,
Typeface.Default, 14, null).Width;
double ColumnWidth(string header, string widestCellText) =>
Math.Max(MeasureWidth(header), MeasureWidth(widestCellText)) + cellChrome;

var lastItem = vm.OcrSubtitleItems.LastOrDefault();
var maxDuration = vm.OcrSubtitleItems.Count > 0 ? vm.OcrSubtitleItems.Max(p => p.Duration) : TimeSpan.Zero;
var numberWidth = ColumnWidth(Se.Language.General.NumberSymbol, vm.OcrSubtitleItems.Count.ToString());
var showWidth = ColumnWidth(Se.Language.General.Show,
fullTimeConverter.Convert(lastItem?.StartTime ?? TimeSpan.Zero, typeof(string), null, CultureInfo.CurrentUICulture) as string ?? string.Empty);
var durationWidth = ColumnWidth(Se.Language.General.Duration,
shortTimeConverter.Convert(maxDuration, typeof(string), null, CultureInfo.CurrentUICulture) as string ?? string.Empty);
var dataGridSubtitle = new TableView
{
AutoGenerateColumns = false,
SelectionMode = DataGridSelectionMode.Extended,
SelectionMode = SelectionMode.Multiple,
CanUserResizeColumns = true,
CanUserSortColumns = true,
HorizontalAlignment = HorizontalAlignment.Stretch,
VerticalAlignment = VerticalAlignment.Stretch,
Width = double.NaN,
Height = double.NaN,
DataContext = vm,
ItemsSource = vm.OcrSubtitleItems,
Columns =
};

if (vm.HasForcedSubtitles)
{
dataGridSubtitle.Columns.Add(new TableViewColumn
{
new DataGridTemplateColumn
{
Header = Se.Language.General.Forced,
IsReadOnly = true,
IsVisible = vm.HasForcedSubtitles,
CellTheme = UiUtil.DataGridNoBorderNoPaddingCellTheme,
CellTemplate = new FuncDataTemplate<OcrSubtitleItem>((_, _) =>
new CheckBox
{
[!ToggleButton.IsCheckedProperty] = new Binding(nameof(OcrSubtitleItem.IsForced)) { Mode = BindingMode.OneWay },
HorizontalAlignment = HorizontalAlignment.Center,
IsHitTestVisible = false,
Focusable = false,
}),
},
new DataGridTextColumn
Header = Se.Language.General.Forced,
Width = new GridLength(MeasureWidth(Se.Language.General.Forced) + cellChrome),
CellTheme = UiUtil.TableViewNoPaddingCellTheme,
HeaderTheme = UiUtil.TableViewColumnHeaderTheme,
CellTemplate = new FuncDataTemplate<OcrSubtitleItem>((_, _) =>
new CheckBox
{
[!ToggleButton.IsCheckedProperty] = new Binding(nameof(OcrSubtitleItem.IsForced)) { Mode = BindingMode.OneWay },
HorizontalAlignment = HorizontalAlignment.Center,
IsHitTestVisible = false,
Focusable = false,
}),
});
}

dataGridSubtitle.Columns.AddRange(new[]
{
new TableViewColumn
{
Header = Se.Language.General.NumberSymbol,
CellTheme = UiUtil.DataGridNoBorderNoPaddingCellTheme,
Width = new GridLength(numberWidth),
CellTheme = UiUtil.TableViewCellTheme,
HeaderTheme = UiUtil.TableViewColumnHeaderTheme,
Binding = new Binding(nameof(OcrSubtitleItem.Number)),
IsReadOnly = true,
},
new DataGridTextColumn
new TableViewColumn
{
Header = Se.Language.General.Show,
CellTheme = UiUtil.DataGridNoBorderNoPaddingCellTheme,
Width = new GridLength(showWidth),
CellTheme = UiUtil.TableViewCellTheme,
HeaderTheme = UiUtil.TableViewColumnHeaderTheme,
Binding = new Binding(nameof(OcrSubtitleItem.StartTime)) { Converter = fullTimeConverter },
IsReadOnly = true,
},
new DataGridTextColumn
new TableViewColumn
{
Header = Se.Language.General.Duration,
CellTheme = UiUtil.DataGridNoBorderNoPaddingCellTheme,
Width = new GridLength(durationWidth),
CellTheme = UiUtil.TableViewCellTheme,
HeaderTheme = UiUtil.TableViewColumnHeaderTheme,
Binding = new Binding(nameof(OcrSubtitleItem.Duration)) { Converter = shortTimeConverter },
IsReadOnly = true,
},
new DataGridTemplateColumn
new TableViewColumn
{
Header = Se.Language.General.Image,
IsReadOnly = true,
CellTheme = UiUtil.DataGridNoBorderNoPaddingCellTheme,
Width = new GridLength(vm.ImageMaxWidth + cellChrome),
CellTheme = UiUtil.TableViewNoPaddingCellTheme,
HeaderTheme = UiUtil.TableViewColumnHeaderTheme,
CellTemplate = new FuncDataTemplate<OcrSubtitleItem>((item, _) =>
{
var stackPanel = new StackPanel
Expand Down Expand Up @@ -497,12 +534,12 @@ private static Border MakeSubtitleView(OcrViewModel vm)
return stackPanel;
})
},
new DataGridTemplateColumn
new TableViewColumn
{
Header = Se.Language.General.Text,
IsReadOnly = true,
Width = new DataGridLength(1, DataGridLengthUnitType.Star),
CellTheme = UiUtil.DataGridNoBorderNoPaddingCellTheme,
Width = new GridLength(1, GridUnitType.Star),
CellTheme = UiUtil.TableViewNoPaddingCellTheme,
HeaderTheme = UiUtil.TableViewColumnHeaderTheme,
CellTemplate = new FuncDataTemplate<OcrSubtitleItem>((item, _) =>
{
var contentPresenter = new ContentPresenter
Expand All @@ -523,16 +560,27 @@ private static Border MakeSubtitleView(OcrViewModel vm)
return contentPresenter;
})
},
},
});

// The image thumbnails scale with Ctrl+plus/minus (Image.MaxWidth/MaxHeight are
// bound to the VM) - keep the pixel-sized image column in step with the zoom.
var imageColumn = dataGridSubtitle.Columns[dataGridSubtitle.Columns.Count - 2];
vm.PropertyChanged += (_, args) =>
{
if (args.PropertyName == nameof(vm.ImageMaxWidth))
{
imageColumn.Width = new GridLength(vm.ImageMaxWidth + cellChrome);
}
};
dataGridSubtitle.Bind(DataGrid.SelectedItemProperty, new Binding(nameof(vm.SelectedOcrSubtitleItem)) { Source = vm });

UiUtil.ApplyTableViewRowStyle(dataGridSubtitle);
dataGridSubtitle.Bind(TableView.SelectedItemProperty, new Binding(nameof(vm.SelectedOcrSubtitleItem)) { Source = vm });
dataGridSubtitle.KeyDown += vm.SubtitleGridKeyDown;
dataGridSubtitle.DoubleTapped += (s, e) => vm.SubtitleGridDoubleTapped();
dataGridSubtitle.AddHandler(InputElement.PointerPressedEvent, vm.DataGridSubtitleMacPointerPressed, Avalonia.Interactivity.RoutingStrategies.Tunnel);
dataGridSubtitle.AddHandler(InputElement.PointerReleasedEvent, vm.DataGridSubtitleMacPointerReleased, Avalonia.Interactivity.RoutingStrategies.Tunnel);
dataGridSubtitle.SelectionChanged += vm.SubtitleGridSelectionChanged;
vm.SubtitleGrid = dataGridSubtitle;
_ = new DataGridCheckboxMultiSelect<OcrSubtitleItem>(dataGridSubtitle);

// Create a Flyout for the DataGrid
var flyout = new MenuFlyout();
Expand Down
28 changes: 28 additions & 0 deletions src/ui/Logic/UiTheme.cs
Original file line number Diff line number Diff line change
Expand Up @@ -734,6 +734,16 @@ private static void ApplyLighterDark()
}
},

// TableView header - same brushes as the DataGrid header above
new Style(x => x.OfType<TableViewColumnHeader>())
{
Setters =
{
new Setter(TableViewColumnHeader.BackgroundProperty, new SolidColorBrush(bgColorHeader)),
new Setter(TableViewColumnHeader.ForegroundProperty, new SolidColorBrush(foreColor))
}
},

// ButtonSpinner
new Style(x => x.OfType<ButtonSpinner>())
{
Expand Down Expand Up @@ -854,6 +864,15 @@ private static void ApplyWindowsClassicGray()
}
},

// TableView header - same brush as the DataGrid header above
new Style(x => x.OfType<TableViewColumnHeader>())
{
Setters =
{
new Setter(TableViewColumnHeader.BackgroundProperty, new SolidColorBrush(headerColor))
}
},

// ButtonSpinner - slightly off-white for consistency (used by TimeCodeUpDown and SecondsUpDown)
new Style(x => x.OfType<ButtonSpinner>())
{
Expand Down Expand Up @@ -969,6 +988,15 @@ private static void ApplyPastel()
}
},

// TableView header - same brush as the DataGrid header above
new Style(x => x.OfType<TableViewColumnHeader>())
{
Setters =
{
new Setter(TableViewColumnHeader.BackgroundProperty, new SolidColorBrush(lightPurple))
}
},

// ButtonSpinner (used by TimeCodeUpDown) with soft pink
new Style(x => x.OfType<ButtonSpinner>())
{
Expand Down
30 changes: 22 additions & 8 deletions src/ui/Logic/UiUtil.cs
Original file line number Diff line number Diff line change
Expand Up @@ -93,25 +93,39 @@ private static ControlTheme GetTableViewCellTheme(bool noPadding)

private static ControlTheme GetTableViewColumnHeaderTheme()
{
var showVertical =
Se.Settings.Appearance.GridLinesAppearance == nameof(DataGridGridLinesVisibility.Vertical) ||
Se.Settings.Appearance.GridLinesAppearance == nameof(DataGridGridLinesVisibility.All);

return new ControlTheme(typeof(TableViewColumnHeader))
{
Setters =
{
new Setter(TableViewColumnHeader.BackgroundProperty, Brushes.Transparent),
// Match the DataGrid header background: the Fluent DataGrid resource by
// default; SE's custom themes (lighter dark, classic gray, pastel) override
// both header types with the same brush via app styles in UiTheme.
new Setter(TableViewColumnHeader.BackgroundProperty, GetDataGridHeaderBackgroundBrush()),
new Setter(TableViewColumnHeader.PaddingProperty, new Thickness(4, 2, 4, 4)),
new Setter(TableViewColumnHeader.BorderBrushProperty, GetBorderBrush()),
// The bottom line always shows: it separates the header from the first row the way
// DataGrid's header does, independently of the horizontal grid-line setting.
new Setter(TableViewColumnHeader.BorderThicknessProperty, new Thickness(0, 0, showVertical ? 1 : 0, 1)),
// Both header lines always show, independently of the grid-lines setting: the
// bottom line separates the header from the first row and the right line
// separates the column headers from each other, the way DataGrid's header
// (with its always-on separators) does.
new Setter(TableViewColumnHeader.BorderThicknessProperty, new Thickness(0, 0, 1, 1)),
new Setter(TableViewColumnHeader.TemplateProperty, TableViewColumnHeaderTemplate),
}
};
}

private static IBrush GetDataGridHeaderBackgroundBrush()
{
if (Application.Current != null &&
Application.Current.TryGetResource("DataGridColumnHeaderBackgroundBrush",
Application.Current.ActualThemeVariant, out var resource) &&
resource is IBrush brush)
{
return brush;
}

return Brushes.Transparent;
}

// Mirrors the built-in header template (content + resize thumb) but routes the border
// properties to the presenter so the header's lines match the cells', and drops the
// thumb's own off-colour separator rectangle.
Expand Down
Loading