Compare commits
72
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ede2774783 | ||
|
|
9209561d20 | ||
|
|
2157e157d3 | ||
|
|
c78a13fe56 | ||
|
|
8034ad2a71 | ||
|
|
1f6835fc53 | ||
|
|
99f26667e7 | ||
|
|
a80bf72ee4 | ||
|
|
04b8f255b1 | ||
|
|
805f6a117a | ||
|
|
7b8d45c229 | ||
|
|
89d2652619 | ||
|
|
96790ea365 | ||
|
|
a97a1bd775 | ||
|
|
e69dc78969 | ||
|
|
824ff609b2 | ||
|
|
d5e07ae893 | ||
|
|
a66e164326 | ||
|
|
822850d557 | ||
|
|
a55bee9798 | ||
|
|
fb367f4b55 | ||
|
|
50fd62c834 | ||
|
|
f8e929aaa8 | ||
|
|
7f61c782a7 | ||
|
|
6586a884f4 | ||
|
|
a7edb1785f | ||
|
|
27b7aa4bf5 | ||
|
|
cbb1425354 | ||
|
|
2416bb376a | ||
|
|
fba6bd2d97 | ||
|
|
fe3377cc79 | ||
|
|
01fd435cde | ||
|
|
a053695f7c | ||
|
|
87b6032be7 | ||
|
|
885d670490 | ||
|
|
366a269dc2 | ||
|
|
86b9e2e6b4 | ||
|
|
d8336938e3 | ||
|
|
561b39dba0 | ||
|
|
824323f6eb | ||
|
|
d26dd10f11 | ||
|
|
ec4f6b16ca | ||
|
|
ecc96b24da | ||
|
|
6109862b37 | ||
|
|
aae27e4142 | ||
|
|
404bd9580a | ||
|
|
03bbb8a558 | ||
|
|
146dfada01 | ||
|
|
4caedf2fee | ||
|
|
05112b89a9 | ||
|
|
7d29ce3d46 | ||
|
|
09c57423a8 | ||
|
|
f61a1f94e2 | ||
|
|
0fc5046dec | ||
|
|
1ec41c531b | ||
|
|
0080947156 | ||
|
|
111c251809 | ||
|
|
9170637ac6 | ||
|
|
696f048c0c | ||
|
|
e5f7247a8e | ||
|
|
9a64b3331b | ||
|
|
a34483d45e | ||
|
|
14a2c4f76f | ||
|
|
cb97889d09 | ||
|
|
ac18e6a694 | ||
|
|
a6f9fbaa3c | ||
|
|
c66dfa85c1 | ||
|
|
3863140edf | ||
|
|
616d90d468 | ||
|
|
7ee6aed043 | ||
|
|
c9a10a1c37 | ||
|
|
025b49b7cd |
No files matched your search
@@ -19,6 +19,7 @@ namespace mbts.ui {
|
||||
$"{nameof(Smashicons)}.{name}.GeometryKey";
|
||||
}
|
||||
public const string
|
||||
AddBoxKey = nameof(AddBoxKey),
|
||||
AddBoxesGeometryKey = nameof(AddBoxesGeometryKey),
|
||||
AddFileGeometryKey = nameof(AddFileGeometryKey),
|
||||
AddFileThinGeometryKey = nameof(AddFileThinGeometryKey),
|
||||
@@ -40,6 +41,7 @@ namespace mbts.ui {
|
||||
EditDocumentAltGeometryKey = nameof(EditDocumentAltGeometryKey),
|
||||
EditDocumentGeometryKey = nameof(EditDocumentGeometryKey),
|
||||
GearGeometryKey = nameof(GearGeometryKey),
|
||||
HamburgerMenuGeometryKey = nameof(HamburgerMenuGeometryKey),
|
||||
HornGeometryKey = nameof(HornGeometryKey),
|
||||
KeyGeometryKey = nameof(KeyGeometryKey),
|
||||
LetterAKey = nameof(LetterAKey),
|
||||
@@ -125,6 +127,7 @@ namespace mbts.ui {
|
||||
|
||||
public static ImageSource Accounting { get { return load_resource_and_freeze("Accounting"); } }
|
||||
public static ImageSource Add { get { return load_resource_and_freeze("Add"); } }
|
||||
public static ImageSource AddBox => load_resource_and_freeze(AddBoxKey);
|
||||
public static ImageSource AddBoxes => create_icon(AddBoxesGeometryKey);
|
||||
public static ImageSource AddEncircled { get { return load_resource_and_freeze("AddEncircled"); } }
|
||||
public static ImageSource AddFile => create_icon(Smashicons.AddFileGeometryKey);
|
||||
@@ -172,6 +175,7 @@ namespace mbts.ui {
|
||||
public static ImageSource FilterActive { get { return load_resource_and_freeze("FilterActive"); } }
|
||||
public static ImageSource FilterError { get { return load_resource_and_freeze("FilterError"); } }
|
||||
public static ImageSource Gear => create_icon(GearGeometryKey);
|
||||
public static ImageSource HamburgerMenu => create_icon(HamburgerMenuGeometryKey);
|
||||
public static ImageSource Horn => create_icon(HornGeometryKey);
|
||||
public static ImageSource Import => load_resource_and_freeze("Import");
|
||||
public static ImageSource Key => create_icon(KeyGeometryKey);
|
||||
|
||||
@@ -13,6 +13,51 @@
|
||||
M19.522852,25.255011L20.920847,25.256994 20.920847,29.729012 19.522852,29.729012z M22.826874,21.952002L25.989905,25.113989 25.001887,26.101996 21.838856,22.941015z M25.269894,19.506992L29.742937,19.509007 29.742937,20.906012 25.269894,20.907995z M10.519729,14.945011C11.300744,14.954991,12.079745,15.103001,12.818766,15.386997L10.878742,17.327C9.6457264,17.200016,8.368704,17.592014,7.4257004,18.532993L3.597655,22.359015C1.9326275,24.025 1.9316508,26.734991 3.597655,28.40299 5.2636887,30.068001 7.9747178,30.066017 9.6397144,28.40299L13.468767,24.575018C14.410794,23.633001,14.800783,22.355993,14.674774,21.122015L16.615807,19.181004C17.523806,21.547003,17.034817,24.325995,15.126776,26.233008L11.298761,30.061012C8.713709,32.644997 4.5236599,32.644997 1.9396465,30.061012 -0.64638097,27.475012 -0.64638097,23.286017 1.9386395,20.702002L5.7666852,16.875004C7.0777046,15.562992,8.8017227,14.922001,10.519729,14.945011z M2.1536381,10.959994L6.6257035,10.959994 6.6266801,12.359988 2.1536381,12.359988z M6.7686803,5.894014L9.9307338,9.0549947 8.9427158,10.044008 5.7806932,6.8819901z M10.976735,2.1390082L12.374761,2.1390082 12.375768,6.6130081 10.975728,6.6130081z M25.382902,0C27.076922,-1.8170738E-08 28.770941,0.6470033 30.063955,1.9389956 32.646992,4.5220043 32.646992,8.7130147 30.061941,11.297L26.234902,15.126011C24.326891,17.032993,21.546862,17.521,19.181845,16.614994L21.121868,14.674015C22.35586,14.798008,23.63389,14.407992,24.575886,13.467013L28.403933,9.6390096C30.067953,7.972994 30.069967,5.2619945 28.403933,3.5960094 26.738905,1.9310002 24.0269,1.9320073 22.361872,3.5960094L18.533827,7.4249893C17.591829,8.3659993,17.201811,9.6440145,17.32782,10.87601L15.385781,12.817996C14.477781,10.453005,14.968784,7.6730062,16.875818,5.7659928L20.702858,1.9379885C21.994864,0.64599622,23.688883,-1.8170738E-08,25.382902,0z
|
||||
</sys:String>
|
||||
|
||||
<sys:String x:Key="{x:Static mbts:Icons.HamburgerMenuGeometryKey}">
|
||||
M2.384186E-06,24L32.000004,24 32.000004,26 2.384186E-06,26z M0,11.999998L32,11.999998 32,13.999998 0,13.999998z M2.384186E-06,0L32.000004,0 32.000004,2 2.384186E-06,2z
|
||||
</sys:String>
|
||||
|
||||
<DrawingImage x:Key="{x:Static mbts:Icons.AddBoxKey}">
|
||||
<DrawingImage.Drawing>
|
||||
<DrawingGroup>
|
||||
<GeometryDrawing Brush="#E0E0E0">
|
||||
<GeometryDrawing.Geometry>
|
||||
<PathGeometry>
|
||||
<PathGeometry.Figures>
|
||||
<PathFigure StartPoint="0,0" IsClosed="True" IsFilled="True">
|
||||
<PolyLineSegment>
|
||||
<PolyLineSegment.Points>
|
||||
<Point>100,0</Point>
|
||||
<Point>100,100</Point>
|
||||
<Point>0,100</Point>
|
||||
</PolyLineSegment.Points>
|
||||
</PolyLineSegment>
|
||||
</PathFigure>
|
||||
</PathGeometry.Figures>
|
||||
</PathGeometry>
|
||||
</GeometryDrawing.Geometry>
|
||||
</GeometryDrawing>
|
||||
<GeometryDrawing Brush="Black">
|
||||
<GeometryDrawing.Pen>
|
||||
<Pen Brush="Black" Thickness="4"/>
|
||||
</GeometryDrawing.Pen>
|
||||
<GeometryDrawing.Geometry>
|
||||
<PathGeometry>
|
||||
<PathGeometry.Figures>
|
||||
<PathFigure StartPoint="25,50">
|
||||
<LineSegment Point="75,50"/>
|
||||
</PathFigure>
|
||||
<PathFigure StartPoint="50,25">
|
||||
<LineSegment Point="50,75"/>
|
||||
</PathFigure>
|
||||
</PathGeometry.Figures>
|
||||
</PathGeometry>
|
||||
</GeometryDrawing.Geometry>
|
||||
</GeometryDrawing>
|
||||
</DrawingGroup>
|
||||
</DrawingImage.Drawing>
|
||||
</DrawingImage>
|
||||
|
||||
<!--AddBoxes (the icon is free to use if we include this information in our credit section)
|
||||
Source: https://www.flaticon.com/authors/gregor-cresnar
|
||||
Ttile: Add free icon
|
||||
|
||||
@@ -116,7 +116,7 @@ namespace mbts.spark {
|
||||
{ Columns.WorkingAsks.ToString(), GridColumnSettingsFeatureFlags.ImmutableFontColor },
|
||||
{ Columns.WorkingBids.ToString(), GridColumnSettingsFeatureFlags.ImmutableFontColor }
|
||||
};
|
||||
grid_settings_manager = new SimpleGridSettingsManager<Row>(columns, col_settings_flags, row_vms);
|
||||
grid_settings_manager = new GridSettingsManager(columns, col_settings_flags);
|
||||
// this will initialize the immutable properties that correspond to the col_settings_flags above (e.g. Bid/Ask font_color_override, etc.)
|
||||
grid_settings_manager.load_from(settings_manager.current_settings());
|
||||
//
|
||||
@@ -159,14 +159,18 @@ namespace mbts.spark {
|
||||
ask_price_filter, ask_merged_size_filter, ask_direct_size_filter, ask_implied_size_filter,
|
||||
last_price_filter, last_price_from_spread_filter, last_size_filter, day_change_filter, settlement_filter,
|
||||
session_low_filter, session_high_filter, position_filter, raw_day_position_filter, working_bid_volume_filter, working_ask_volume_filter, volume_filter, security_key_filter),
|
||||
settings_manager.subscribe_current().OnDispatcher().Subscribe(grid_settings_manager),
|
||||
settings_manager.subscribe_current().OnDispatcher().Subscribe(s => {
|
||||
grid_settings_manager.load_from(s);
|
||||
foreach (var r in row_vms)
|
||||
r.raise_invalidated();
|
||||
}),
|
||||
WorkLoop.Start("MarketView", _refresh)
|
||||
);
|
||||
}
|
||||
|
||||
public ObservableCollection<ColumnBase> columns { get; } = new ObservableCollection<ColumnBase>();
|
||||
|
||||
public SimpleGridSettingsManager<Row> grid_settings_manager { get; }
|
||||
public GridSettingsManager grid_settings_manager { get; }
|
||||
|
||||
public GridSettings get_grid_save_state() {
|
||||
if (_settings_manager.instance_settings == null)
|
||||
|
||||
@@ -7,7 +7,6 @@ using bts.utils;
|
||||
using mbts.persist;
|
||||
using mbts.ui;
|
||||
using mbts.ui.settings;
|
||||
using mbts.ui.shell;
|
||||
using mbts.ui.spark;
|
||||
using mbts.ui.utility;
|
||||
using mbts.utils;
|
||||
@@ -79,7 +78,7 @@ namespace mbts.spark {
|
||||
if (match is ui.controls.DIYGrid.IKeyedColumn<MarketViewView.Columns> kcol) {
|
||||
var col_comp = MarketViewVM.get_column_comparer(kcol.key);
|
||||
if (col_comp != null) {
|
||||
comp_builder.add(sc.is_ascending ? col_comp : col_comp.reverse());
|
||||
comp_builder.Add(sc.is_ascending ? col_comp : col_comp.reverse());
|
||||
} else {
|
||||
Log.Ui.warn($"{_window_type} is unable to find the row comparer for the '{sc.col_name}' column.");
|
||||
}
|
||||
@@ -146,16 +145,17 @@ namespace mbts.spark {
|
||||
SparkCommands.change_securities_command(() => (update_securities_cmd, _state.security_criteria)),
|
||||
// wrap the grid settings view factory in a dialogCommand here since the vm is destroyed/recreated when the security criteria changes
|
||||
SparkCommands.window_settings_command(new DialogCommand(() => {
|
||||
var vm = new ShellSettingsDialogVM<GridSettings, SimpleGridSettingsVM>(
|
||||
var vm = new ShellSettingsDialogVM<GridSettings, GridSettingsVM>(
|
||||
_settings_manager,
|
||||
us => us.market_view_grid,
|
||||
UserSettings.FileExtensions.GRID,
|
||||
s => _vm.grid_settings_manager.create_vm(
|
||||
s => {
|
||||
var gs_vm = _vm.grid_settings_manager.create_vm();
|
||||
// we need to get the most up to date GridSettings when launching b/c users may
|
||||
// have changed some settings on the grid directly, like column widths.
|
||||
s.with(_vm.columns.Select(c => c.create()))
|
||||
)
|
||||
);
|
||||
gs_vm.load(s.with(_vm.columns.Select(c => c.create())));
|
||||
return gs_vm;
|
||||
});
|
||||
vm.SubscribeDisposed(literal_title_source.OnDispatcher().Subscribe(t => vm.title = t));
|
||||
var dialog = new ShellSettingsDialog { DataContext = vm };
|
||||
vm.RequestClose += (s, e) => WindowUtilities.force_close(dialog);
|
||||
|
||||
@@ -25,7 +25,6 @@ namespace mbts.spark {
|
||||
readonly TimeAndSalesModel _model;
|
||||
IWindowSettingsManager<GridSettings> _settings_manager;
|
||||
|
||||
|
||||
public TimeAndSalesVM(IBus bus, IWindowSettingsManager<GridSettings> settings_manager, TimeAndSalesModel model, StartupData startup_data)
|
||||
: base(RXCollection.CreateBuffer(model.trades, 1000), Enumerable.Empty<SortingCriteria>()) {
|
||||
_bus = bus;
|
||||
@@ -52,23 +51,26 @@ namespace mbts.spark {
|
||||
columns.Add(new RenderTextColumn<Row, TimeAndSalesView.Columns>(col_key, r => r[col_key], filter, filter_template, r => _get_aggressor_brush(r))
|
||||
{ id = col_id(col_key), Header = header, Width = width, sort_member_path = sort_member_path, horizontal_alignment = h_alignment }
|
||||
);
|
||||
|
||||
|
||||
// be sure to load GridSettings AFTER the columns collection has been populated, the grid settings manager looks columns by id to apply the appropriate settings,
|
||||
// if the collection is not populated, the column settings will not be applied
|
||||
grid_settings_manager = new SimpleGridSettingsManager<Row>(columns, new Dictionary<string, GridColumnSettingsFeatureFlags> { }, row_vms);
|
||||
grid_settings_manager = new GridSettingsManager(columns);
|
||||
grid_settings_manager.load_from(settings_manager.current_settings());
|
||||
_resources.AddRange(
|
||||
show_ladder,
|
||||
DataGridFilterHelper.hook_filters(
|
||||
rows_view, time_filter, security_filter, price_filter, size_filter, aggressor_filter, security_key_filter),
|
||||
settings_manager.subscribe_current().OnDispatcher().Subscribe(grid_settings_manager)
|
||||
);
|
||||
settings_manager.subscribe_current().OnDispatcher().Subscribe(s => {
|
||||
grid_settings_manager.load_from(s);
|
||||
foreach (var r in row_vms)
|
||||
r.raise_invalidated();
|
||||
}));
|
||||
rows_view.set_sort_descriptions(startup_data.sorting_criterion, columns);
|
||||
}
|
||||
|
||||
public ObservableCollection<DIYGrid.ColumnBase> columns { get; } = new ObservableCollection<DIYGrid.ColumnBase>();
|
||||
public DelegateCommandParamObserver<Row> show_ladder { get; }
|
||||
public SimpleGridSettingsManager<Row> grid_settings_manager { get; }
|
||||
public GridSettingsManager grid_settings_manager { get; }
|
||||
|
||||
public GridSettings get_grid_save_state() {
|
||||
if (_settings_manager.instance_settings == null)
|
||||
|
||||
@@ -7,7 +7,6 @@ using mbts.co2;
|
||||
using mbts.persist;
|
||||
using mbts.ui;
|
||||
using mbts.ui.settings;
|
||||
using mbts.ui.shell;
|
||||
using mbts.ui.spark;
|
||||
using mbts.ui.utility;
|
||||
using mbts.utils;
|
||||
@@ -70,16 +69,17 @@ namespace mbts.spark {
|
||||
SparkCommands.change_securities_command(() => (update_securities_cmd, _state.security_criteria)),
|
||||
// wrap the grid settings view factory in a dialogCommand here since the vm is destroyed/recreated when the security criteria changes
|
||||
SparkCommands.window_settings_command(new DialogCommand(() => {
|
||||
var vm = new ShellSettingsDialogVM<GridSettings, SimpleGridSettingsVM>(
|
||||
_settings_manager,
|
||||
s => s.time_and_sales_grid,
|
||||
UserSettings.FileExtensions.GRID,
|
||||
s => _vm.value.grid_settings_manager.create_vm(
|
||||
// we need to get the most up to date GridSettings when launching b/c users may
|
||||
// have changed some settings on the grid directly, like column widths.
|
||||
s.with(_vm.value.columns.Select(c => c.create()))
|
||||
)
|
||||
);
|
||||
var vm = new ShellSettingsDialogVM<GridSettings, GridSettingsVM>(
|
||||
_settings_manager,
|
||||
s => s.time_and_sales_grid,
|
||||
UserSettings.FileExtensions.GRID,
|
||||
s => {
|
||||
var gs_vm = _vm.value.grid_settings_manager.create_vm();
|
||||
// we need to get the most up to date GridSettings when launching b/c users may
|
||||
// have changed some settings on the grid directly, like column widths.
|
||||
gs_vm.load(s.with(_vm.value.columns.Select(c => c.create())));
|
||||
return gs_vm;
|
||||
});
|
||||
vm.SubscribeDisposed(literal_title_source.OnDispatcher().Subscribe(t => vm.title = t));
|
||||
var dialog = new ShellSettingsDialog { DataContext = vm };
|
||||
vm.RequestClose += (s, e) => WindowUtilities.force_close(dialog);
|
||||
|
||||
@@ -76,19 +76,20 @@ namespace mbts.spark {
|
||||
};
|
||||
return ob_settings;
|
||||
}), SparkCommands.window_settings_command(new DialogCommand(() => {
|
||||
var vm = new ShellSettingsDialogVM<GridSettings, SimpleGridSettingsVM>(
|
||||
var dialog_vm = new ShellSettingsDialogVM<GridSettings, GridSettingsVM>(
|
||||
_settings_manager,
|
||||
us => us.synthetic_management_grid,
|
||||
UserSettings.FileExtensions.GRID,
|
||||
s => _vm.value.grid_settings_manager.create_vm(
|
||||
s => {
|
||||
var gs_vm = _vm.value.grid_settings_manager.create_vm();
|
||||
// we need to get the most up to date GridSettings when launching b/c users may
|
||||
// have changed some settings on the grid directly, like column widths.
|
||||
s.with(_vm.value.columns.Select(c => c.create()))
|
||||
)
|
||||
);
|
||||
vm.SubscribeDisposed(literal_title_source.OnDispatcher().Subscribe(t => vm.title = t));
|
||||
var dialog = new ShellSettingsDialog { Title = "Synthetic Management Grid Settings", DataContext = vm };
|
||||
vm.RequestClose += (s, e) => WindowUtilities.force_close(dialog);
|
||||
gs_vm.load(s.with(_vm.value.columns.Select(c => c.create())));
|
||||
return gs_vm;
|
||||
});
|
||||
dialog_vm.SubscribeDisposed(literal_title_source.OnDispatcher().Subscribe(t => dialog_vm.title = t));
|
||||
var dialog = new ShellSettingsDialog { DataContext = dialog_vm };
|
||||
dialog_vm.RequestClose += (s, e) => WindowUtilities.force_close(dialog);
|
||||
return dialog;
|
||||
}))
|
||||
};
|
||||
|
||||
@@ -68,19 +68,23 @@ namespace mbts.spark {
|
||||
Width = width
|
||||
});
|
||||
//
|
||||
grid_settings_manager = new SimpleGridSettingsManager<Row>(columns, DictionaryHelper.empty<string, GridColumnSettingsFeatureFlags>(), row_vms);
|
||||
grid_settings_manager = new GridSettingsManager(columns);
|
||||
// this will initialize the immutable properties that correspond to the col_settings_flags above (e.g. Bid/Ask font_color_override, etc.)
|
||||
grid_settings_manager.load_from(settings_manager.current_settings());
|
||||
|
||||
_resources.AddRange(
|
||||
orders_model.admin_model.all_user_tuples.OnDispatcher().Subscribe(user_filter_items_observer),
|
||||
DataGridFilterHelper.hook_all<SyntheticManagementDialogVM, Row>(this, rows_view),
|
||||
settings_manager.subscribe_current().OnDispatcher().Subscribe(grid_settings_manager)
|
||||
settings_manager.subscribe_current().OnDispatcher().Subscribe(s => {
|
||||
grid_settings_manager.load_from(s);
|
||||
foreach (var r in row_vms)
|
||||
r.raise_invalidated();
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
public ObservableCollection<ColumnBase> columns { get; } = new ObservableCollection<ColumnBase>();
|
||||
public SimpleGridSettingsManager<Row> grid_settings_manager { get; }
|
||||
public GridSettingsManager grid_settings_manager { get; }
|
||||
|
||||
public IDataGridColumnPersistentFilter<Row, TimeFilterTuple> create_time_filter { get; } = new TimeFilterVM<Row>(r => r.create_time, nameof(Row.create_time));
|
||||
public IDataGridColumnPersistentFilter<Row, string> name_filter { get; } = new RegexFilterVM<Row>(r => r.name, nameof(Row.name));
|
||||
|
||||
@@ -153,8 +153,13 @@
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="*"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
</Grid.RowDefinitions>
|
||||
<ListBox
|
||||
x:Name="_tree_view"
|
||||
Grid.RowSpan="2"
|
||||
ItemsSource="{Binding definitions_view}"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Stretch"
|
||||
@@ -173,7 +178,7 @@
|
||||
</DataTemplate>
|
||||
</ListBox.ItemTemplate>
|
||||
</ListBox>
|
||||
<ScrollViewer Grid.Column="1" Grid.IsSharedSizeScope="True" Margin="8,4">
|
||||
<ScrollViewer Grid.Column="1" Grid.IsSharedSizeScope="True" VerticalScrollBarVisibility="Auto" Margin="8,4,8,0">
|
||||
<Grid x:Name="edit_pane">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/>
|
||||
@@ -429,19 +434,25 @@
|
||||
Margin="0,6,0,0"
|
||||
Padding="12,8"
|
||||
/>
|
||||
<StackPanel Grid.Row="3" HorizontalAlignment="Right" Orientation="Horizontal">
|
||||
<StackPanel.Margin>
|
||||
<Thickness Left="{StaticResource DialogSkirtPadding.Left}" Top="{StaticResource VerticalPush2}" Right="{StaticResource DialogSkirtButtonPadding.Right}" Bottom="{StaticResource DialogSkirtPadding.Bottom}"/>
|
||||
</StackPanel.Margin>
|
||||
<Button ToolTipService.ShowOnDisabled="True" Margin="{StaticResource DialogSkirtButtonMargin}" Command="{Binding save_cmd}" CommandParameter="{Binding selected_definition}" Width="100" ToolTip="{Binding save_cmd_tooltip}" IsDefault="True">
|
||||
<TextBlock Text="{Binding selected_definition.name, StringFormat='{}Save {0}'}"/>
|
||||
</Button>
|
||||
<Button ToolTipService.ShowOnDisabled="True" Margin="{StaticResource DialogSkirtButtonMargin}" Command="{Binding revert_cmd}" CommandParameter="{Binding selected_definition}" Width="100" ToolTip="{Binding revert_cmd_tooltip}">
|
||||
<TextBlock Text="{Binding selected_definition.name, StringFormat='{}Revert {0}'}"/>
|
||||
</Button>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</ScrollViewer>
|
||||
<c:DialogSkirt
|
||||
Grid.Row="1"
|
||||
Grid.Column="1"
|
||||
Background="Transparent"
|
||||
BorderThickness="0"
|
||||
>
|
||||
<c:DialogSkirt.RightItems>
|
||||
<x:Array Type="{x:Type Button}">
|
||||
<Button ToolTipService.ShowOnDisabled="True" Margin="{StaticResource DialogSkirtButtonMargin}" Command="{Binding save_cmd}" CommandParameter="{Binding selected_definition}" Width="100" ToolTip="{Binding save_cmd_tooltip}" IsDefault="True">
|
||||
Save Algo
|
||||
</Button>
|
||||
<Button ToolTipService.ShowOnDisabled="True" Margin="{StaticResource DialogSkirtButtonMargin}" Command="{Binding revert_cmd}" CommandParameter="{Binding selected_definition}" Width="100" ToolTip="{Binding revert_cmd_tooltip}">
|
||||
Revert Algo
|
||||
</Button>
|
||||
</x:Array>
|
||||
</c:DialogSkirt.RightItems>
|
||||
</c:DialogSkirt>
|
||||
</Grid>
|
||||
</AdornerDecorator>
|
||||
</UserControl>
|
||||
@@ -9,6 +9,7 @@ using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reactive.Disposables;
|
||||
using System.Reactive.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using StartupData = bts.shell.AlgoStudioWindowFactory.StartupData;
|
||||
|
||||
@@ -68,23 +69,12 @@ namespace mbts.spark {
|
||||
.Subscribe(sec_map => _view.Resources[ResourceKeys.Securities] = sec_map));
|
||||
// add definition
|
||||
_commands.Add(new SparkCommandCommand("Add Algo", Icons.AddBoxes, _vm.add_definition_cmd));
|
||||
// clone definition
|
||||
var clone_selected_definition_spark_cmd = new SparkCommandCommand(string.Empty, Icons.DocumentCopy, _vm.clone_selected_definition_cmd);
|
||||
_commands.Add(clone_selected_definition_spark_cmd);
|
||||
_resources.Add(_vm.subscribe_definition().OnDispatcher().Subscribe(
|
||||
def => clone_selected_definition_spark_cmd.title = def == null ? $"Clone (None Selected)" : $"Clone '{def.name}'"));
|
||||
// save
|
||||
if (_ui_configuration.can_save_specific) {
|
||||
var content_is_dirty_src = _vm.subscribe_definition().SelectManyInSeries(def => def?.subscribe_is_definition_dirty() ?? Observable.Return(false));
|
||||
_commands.Add(new ReactiveSparkCommand(_vm.subscribe_definition().OnDispatcher().Select(c => $"Save '{c?.name}'"), Icons.DiskFile, () => {
|
||||
if (_vm.selected_definition is AlgoDefinitionVM d)
|
||||
_vm.save_definition(d).ignore();
|
||||
}, content_is_dirty_src));
|
||||
}
|
||||
// save all
|
||||
var save_all_cmd = new ObservantAsyncCommand(_vm.save_all);
|
||||
_commands.Add(new SparkCommandCommand("Save All", Icons.Disk, save_all_cmd));
|
||||
_resources.Add(_vm.subscribe_dirty().OnDispatcher().Subscribe(save_all_cmd));
|
||||
_commands.Add(new SparkCommandCommand("Clone Algo", Icons.DocumentCopy, _vm.clone_selected_definition_cmd));
|
||||
_commands.Add(new SparkCommandCommand("Save Algo", Icons.DiskFile, _vm.save_cmd) {
|
||||
show_in_toolbar = _ui_configuration.can_save_specific
|
||||
});
|
||||
_commands.Add(new SparkCommandCommand("Save All", Icons.Disk, ObservantAsyncCommand.create(_vm.save_all, out var save_all_observer)));
|
||||
_resources.Add(_vm.subscribe_dirty().OnDispatcher().Subscribe(save_all_observer));
|
||||
// undo/redo
|
||||
if (_ui_configuration.can_undo_redo) {
|
||||
// undo
|
||||
@@ -117,30 +107,11 @@ namespace mbts.spark {
|
||||
_resources.Add(can_redo_source.Subscribe(redo_cmd));
|
||||
}
|
||||
// remove
|
||||
var remove_spark_cmd = new ObservantSparkCommandCommand<IAlgoStudioContentVM>(Icons.Trash, v => $"Remove '{v?.name}'", v => v != null, value => {
|
||||
if (value is AlgoDefinitionVM d) {
|
||||
string prompt; MessageBoxImage icon;
|
||||
if (d.key.mode == AlgoKey.Mode.Persisted) {
|
||||
prompt = $"Are you sure you want to delete '{d.name}?'";
|
||||
icon = MessageBoxImage.Question;
|
||||
} else {
|
||||
// algo hasn't even been created yet (just an unsaved placeholder item), no need to get user confirmation in this case.
|
||||
prompt = null;
|
||||
icon = default;
|
||||
}
|
||||
if (prompt != null) {
|
||||
var resp = BTSMessageBox.Show(prompt, "Algo Studio", BTSMessageBoxButtons.Yes | BTSMessageBoxButtons.No, icon);
|
||||
switch (resp) {
|
||||
case BTSMessageBoxButtons.Yes: break;
|
||||
case BTSMessageBoxButtons.No:
|
||||
default: return;
|
||||
}
|
||||
}
|
||||
_vm.remove_definition(d).ignore();
|
||||
}
|
||||
});
|
||||
_commands.Add(remove_spark_cmd);
|
||||
_resources.Add(_vm.subscribe_definition().OnDispatcher().Subscribe(remove_spark_cmd));
|
||||
_commands.Add(new SparkCommandCommand("Remove Algo", Icons.Trash, ObservantAsyncCommand.create<AlgoDefinitionVM>(
|
||||
vm => BTSMessageBox.Confirm($"Are you sure you want to delete '{vm.name}?'", Title) ? _vm.remove_definition(vm) : Task.CompletedTask,
|
||||
vm => vm != null,
|
||||
out var remove_cmd_observer)));
|
||||
_resources.Add(_vm.subscribe_definition().OnDispatcher().Subscribe(remove_cmd_observer));
|
||||
}
|
||||
|
||||
void _dispose_core() {
|
||||
@@ -149,4 +120,4 @@ namespace mbts.spark {
|
||||
Disposable.dispose(ref _vm);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -38,10 +38,10 @@ namespace mbts.spark {
|
||||
IWindowSettingsManager<GridSettings> _settings_manager;
|
||||
int _total_row_count;
|
||||
|
||||
public FillsVM(IBus bus, OrdersModel model, IObservable<FillsSettings> state_obs, IWindowSettingsManager<GridSettings> settings_manager)
|
||||
: this(bus, model, state_obs, _get_observable(bus, model, state_obs), settings_manager) { }
|
||||
public FillsVM(IBus bus, OrdersModel model, FillsSettings settings, IWindowSettingsManager<GridSettings> settings_manager)
|
||||
: this(bus, model, settings, _get_observable(bus, model, settings), settings_manager) { }
|
||||
|
||||
public FillsVM(IBus bus, OrdersModel model, IObservable<FillsSettings> state_obs, IObservable<TableUpdate<FillTuple, string>> fills_source, IWindowSettingsManager<GridSettings> settings_manager) {
|
||||
public FillsVM(IBus bus, OrdersModel model, FillsSettings settings, IObservable<TableUpdate<FillTuple, string>> fills_source, IWindowSettingsManager<GridSettings> settings_manager) {
|
||||
_model = model;
|
||||
_settings_manager = settings_manager;
|
||||
/// Columns
|
||||
@@ -132,7 +132,10 @@ namespace mbts.spark {
|
||||
// rows
|
||||
_rows_manager = DIYGridRowsManager.create(FillsWindow.window_type_name, Columns.FillKey, col_tups, col_tups.WrapSelector(t => (t.col, t.key)), _get_column_comparer, fs => fs.build_predicate());
|
||||
// grid settings
|
||||
grid_settings_manager = new SimpleGridSettingsManager<Row>(columns, new Dictionary<string, GridColumnSettingsFeatureFlags> { }, rows);
|
||||
var col_flags = EnumHelper.get_values<Columns>().ToDictionary(
|
||||
c => c.ToString(),
|
||||
c => GridColumnSettingsFeatureFlags.ImmutableBackgroundColor);
|
||||
grid_settings_manager = new GridSettingsManager(columns, col_flags);
|
||||
// this will initialize the immutable properties that correspond to the col_settings_flags above (e.g. Bid/Ask font_color_override, etc.)
|
||||
if (_settings_manager != null)
|
||||
grid_settings_manager.load_from(_settings_manager.current_settings());
|
||||
@@ -145,6 +148,19 @@ namespace mbts.spark {
|
||||
_unfiltered_fills_changed += o.OnNext;
|
||||
return Disposable.Create(() => _unfiltered_fills_changed -= o.OnNext);
|
||||
});
|
||||
if (settings.grid != null && _settings_manager != null) {
|
||||
// this means we are not using defaults,
|
||||
// Note: settings_manager in turn notifies grid_settings_manager of this update
|
||||
_settings_manager.instance_settings = settings.grid;
|
||||
}
|
||||
_rows_manager.on_settings(settings.filters ?? new FillsFilterStates {
|
||||
fill_time = new TimeFilterTuple(
|
||||
TimeFilterMode.CMEOpen,
|
||||
TimeTextTuple.FromDateTime(DateTime.Today),
|
||||
TimeTextTuple.FromDateTime(DateTime.Today.AddDays(1)))
|
||||
},
|
||||
settings.sorting_criterion);
|
||||
// resources/subscriptions
|
||||
_dispose = new CompositeDisposable(
|
||||
ObservableAutoWrapper.connect(col_tups, columns, ct => ct.col),
|
||||
ObservableAutoWrapper.connect_item_hooks(col_tups, ct => ct.filter as IDisposable),
|
||||
@@ -154,22 +170,12 @@ namespace mbts.spark {
|
||||
Set(ref _total_row_count, t.state.Count, nameof(total_row_count));
|
||||
_unfiltered_fills_changed?.Invoke(t);
|
||||
}),
|
||||
state_obs.OnDispatcher().Subscribe(s => {
|
||||
if (s.grid != null && _settings_manager != null) {
|
||||
// this means we are not using defaults,
|
||||
// Note: settings_manager in turn notifies grid_settings_manager of this update
|
||||
_settings_manager.instance_settings = s.grid;
|
||||
}
|
||||
var fs = s.filters ?? new FillsFilterStates {
|
||||
fill_time = new TimeFilterTuple(
|
||||
TimeFilterMode.CMEOpen,
|
||||
TimeTextTuple.FromDateTime(DateTime.Today),
|
||||
TimeTextTuple.FromDateTime(DateTime.Today.AddDays(1)))
|
||||
};
|
||||
_rows_manager.on_settings(fs, s.sorting_criterion);
|
||||
}),
|
||||
model.subscribe_all_algos().SelectKeyValue(AlgoDataVM.from_data, d => AlgoID.from_algo_key(d.data.key)).OnDispatcher().Subscribe(root_algo_type_observer),
|
||||
settings_manager == null ? Disposable.empty : settings_manager.subscribe_current().OnDispatcher().Subscribe(grid_settings_manager)
|
||||
settings_manager == null ? Disposable.empty : settings_manager.subscribe_current().OnDispatcher().Subscribe(s => {
|
||||
grid_settings_manager.load_from(s);
|
||||
foreach (var r in rows)
|
||||
r.raise_invalidated();
|
||||
})
|
||||
);
|
||||
// local methods
|
||||
void add_col<TFilter>(Columns col_key, string header, Func<Row, string> get_text, IDataGridColumnPersistentFilter<FillTuple, TFilter> filter, string filter_template, double width) =>
|
||||
@@ -192,7 +198,7 @@ namespace mbts.spark {
|
||||
public ObservableCollection<Row> rows { get; } = new ObservableCollection<Row>();
|
||||
public int total_row_count => _total_row_count;
|
||||
|
||||
public SimpleGridSettingsManager<Row> grid_settings_manager { get; }
|
||||
public GridSettingsManager grid_settings_manager { get; }
|
||||
|
||||
public GridSettings get_grid_save_state() {
|
||||
if (_settings_manager.instance_settings == null)
|
||||
@@ -282,6 +288,8 @@ namespace mbts.spark {
|
||||
account = fill_tuple.account;
|
||||
}
|
||||
|
||||
public event Action invalidated;
|
||||
|
||||
public DateTime fill_time => _fill.Timestamp.to_local_time();
|
||||
public TimeInForce time_in_force => _order.time_in_force();
|
||||
public OrderType order_type => _order.order_type();
|
||||
@@ -310,8 +318,8 @@ namespace mbts.spark {
|
||||
public override bool Equals(object obj) => Equals(obj as Row);
|
||||
public override int GetHashCode() => _fill.FillKey.GetHashCode() ^ _order.Key.GetHashCode();
|
||||
|
||||
public event Action invalidated;
|
||||
void IRowVM.raise_invalidated() => invalidated?.Invoke();
|
||||
public void raise_invalidated() =>
|
||||
invalidated?.Invoke();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -26,7 +26,6 @@ namespace mbts.spark {
|
||||
FillsView _view;
|
||||
FillsSettings _state;
|
||||
IReadOnlyList<SparkCommand> _commands;
|
||||
IDisposable _subscriptions;
|
||||
|
||||
public FillsWindow(FillsWindowFactory factory, IBus bus, OrdersModel orders_model, FillsSettings state, WindowSettingsManager<GridSettings> settings_manager)
|
||||
: base("Fills", bus, factory, SparkCommands.ShowFills().icon) {
|
||||
@@ -36,8 +35,6 @@ namespace mbts.spark {
|
||||
thread_id = config.Configuration.get<bts.config.MultiThreadedWindows>().fills ? "Fills" : null;
|
||||
}
|
||||
|
||||
event Action<FillsSettings> _state_changed;
|
||||
|
||||
public override string window_type => window_type_name;
|
||||
public override UIElement Content => _view;
|
||||
public override IEnumerable<SparkCommand> Commands => _commands;
|
||||
@@ -54,38 +51,29 @@ namespace mbts.spark {
|
||||
public void thread_init() {
|
||||
_init_guard.check_access();
|
||||
Log.notnull(_state);
|
||||
var state_src = Observable.FromEvent<Action<FillsSettings>, FillsSettings>(cb => _state_changed += cb, cb => _state_changed -= cb).StartWith(_state);
|
||||
_vm = new FillsVM(_bus, _orders_model, state_src, _settings_manager);
|
||||
_vm = new FillsVM(_bus, _orders_model, _state, _settings_manager);
|
||||
_view = new FillsView { DataContext = _vm };
|
||||
|
||||
_commands = new List<SparkCommand> {
|
||||
// wrap the grid settings view factory in a dialogCommand here since the vm is destroyed/recreated when the security criteria changes
|
||||
SparkCommands.window_settings_command(new DialogCommand(() => {
|
||||
var vm = new ShellSettingsDialogVM<GridSettings, SimpleGridSettingsVM>(
|
||||
var vm = new ShellSettingsDialogVM<GridSettings, GridSettingsVM>(
|
||||
_settings_manager,
|
||||
us => us.fills_grid,
|
||||
UserSettings.FileExtensions.GRID,
|
||||
s => _vm.grid_settings_manager.create_vm(
|
||||
s => {
|
||||
var gs_vm = _vm.grid_settings_manager.create_vm();
|
||||
// we need to get the most up to date GridSettings when launching b/c users may
|
||||
// have changed some settings on the grid directly, like column widths.
|
||||
s.with(_vm.columns.Select(c => c.create()))
|
||||
)
|
||||
);
|
||||
gs_vm.load(s.with(_vm.columns.Select(c => c.create())));
|
||||
return gs_vm;
|
||||
});
|
||||
vm.SubscribeDisposed(literal_title_source.OnDispatcher().Subscribe(t => vm.title = t));
|
||||
var dialog = new ShellSettingsDialog { DataContext = vm };
|
||||
vm.RequestClose += (s, e) => WindowUtilities.force_close(dialog);
|
||||
return dialog;
|
||||
}))
|
||||
};
|
||||
_subscriptions = Disposable.Create(() =>
|
||||
_state_changed?.Invoke(
|
||||
_state = new FillsSettings(_state.orders,
|
||||
_vm.get_sorting_criterion().ToList(),
|
||||
_vm.get_filters(),
|
||||
_vm.grid_settings_manager.create()
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
public override void Dispose() {
|
||||
|
||||
@@ -6,6 +6,7 @@ using mbts.config;
|
||||
using mbts.persist;
|
||||
using mbts.spark;
|
||||
using mbts.ui;
|
||||
using mbts.ui.converters;
|
||||
using mbts.ui.shell;
|
||||
using mbts.ui.spark;
|
||||
using ProtoBuf;
|
||||
@@ -18,6 +19,7 @@ using System.Reactive.Linq;
|
||||
using System.Reactive.Subjects;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Data;
|
||||
using LadderSettings = mbts.spark.LadderSettings;
|
||||
|
||||
namespace bts.shell {
|
||||
@@ -214,7 +216,21 @@ namespace bts.shell {
|
||||
settings_id);
|
||||
dialog_vm.applied += apply;
|
||||
// dialog
|
||||
var dialog = new ShellSettingsDialog { DataContext = dialog_vm };
|
||||
var dialog = new ShellSettingsDialog {
|
||||
DataContext = dialog_vm,
|
||||
};
|
||||
dialog.SetBinding(FrameworkElement.MinWidthProperty, new Binding(nameof(Window.SizeToContent)) {
|
||||
Source = dialog,
|
||||
Converter = new WindowMinWidthConverter {
|
||||
SizeToContentThreshold = 657
|
||||
}
|
||||
});
|
||||
dialog.SetBinding(FrameworkElement.MinHeightProperty, new Binding(nameof(Window.SizeToContent)) {
|
||||
Source = dialog,
|
||||
Converter = new WindowMinHeightConverter {
|
||||
SizeToContentThreshold = 331
|
||||
}
|
||||
});
|
||||
dialog_vm.SubscribeDisposed(title_source.OnDispatcher().Subscribe(t => dialog_vm.title = t));
|
||||
dialog.Resources[ShellSettingsDialog.SecurityMapKey] = _securities;
|
||||
dialog_vm.RequestClose += on_request_close;
|
||||
|
||||
@@ -12,6 +12,7 @@ using mbts.ui.controls.color_picker;
|
||||
using mbts.ui.shell;
|
||||
using mbts.ui.spark;
|
||||
using mbts.ui.utility;
|
||||
using mbts.ui.viewmodels;
|
||||
using mbts.utils;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
@@ -105,12 +106,13 @@ namespace mbts.spark {
|
||||
};
|
||||
// update
|
||||
_update = new DeferrableActionWrapper(() => result_changed?.Invoke());
|
||||
mini_ob.result_changed += _update.execute;
|
||||
foreach (var c in _observable_components)
|
||||
c.PropertyChanged += on_prop_changed;
|
||||
favorite_algos_vm.items.CollectionChanged += on_favorite_algo_items_CollectionChanged;
|
||||
_algo_definitions.AddRange(_model.factory.default_algos(security).Values.Select(algo_factory));
|
||||
_dispose = new CompositeDisposable(
|
||||
mini_ob.Observe().Subscribe(_ => _update.execute()),
|
||||
ObservableAutoWrapper.connect_item_hooks(mini_ob.columns, c => c.Observe().Subscribe(_ => _update.execute())),
|
||||
_model.factory.subscribe_accounts().OnDispatcher().Subscribe(acc_map => {
|
||||
_accounts.SyncWithDictionary(MappedDictionary.create(acc_map, account_factory));
|
||||
_try_select_desired_account();
|
||||
@@ -130,7 +132,6 @@ namespace mbts.spark {
|
||||
return Disposable.Create(() => item.PropertyChanged -= on_prop_changed);
|
||||
}
|
||||
void dispose_resources() {
|
||||
mini_ob.result_changed -= _update.execute;
|
||||
mini_ob.Dispose();
|
||||
favorite_algos_vm.items.CollectionChanged -= on_favorite_algo_items_CollectionChanged;
|
||||
favorite_algos_vm.Dispose();
|
||||
@@ -163,7 +164,7 @@ namespace mbts.spark {
|
||||
}
|
||||
|
||||
public MarketSizeDisplayVM size_display { get; } = new MarketSizeDisplayVM();
|
||||
public SimpleGridSettingsVM mini_ob { get; } = new SimpleGridSettingsVM(OrderBookGridSettingsManager.hard_column_flags);
|
||||
public GridSettingsVM mini_ob { get; } = new MiniOrderBookGridSettingsVM(OrderBookGridSettingsManager.hard_column_flags);
|
||||
// price format
|
||||
public LadderSettings.NetChangeTerms price_format { get => _price_format; set => Set(ref _price_format, value); }
|
||||
public IEnumerable<LadderSettings.NetChangeTerms> price_format_options => EnumHelper.get_values<LadderSettings.NetChangeTerms>();
|
||||
@@ -305,7 +306,7 @@ namespace mbts.spark {
|
||||
favorite_algos_vm.load_from(settings.favorite_algos);
|
||||
size_display.load_from(settings.size_display);
|
||||
size_presets.SetItems(settings.calculator_buttons.empty_if_null().Select(s => _create_size_preset_vm((uint)s)));
|
||||
mini_ob.load_from(settings.mini_ob.value);
|
||||
mini_ob.load(settings.mini_ob.value);
|
||||
// columns
|
||||
_orders.assign_from(_current.orders_column);
|
||||
_bid.assign_from(_current.bid_column);
|
||||
@@ -363,14 +364,14 @@ namespace mbts.spark {
|
||||
LadderSettings clone;
|
||||
if (_current == null) {
|
||||
clone = new LadderSettings {
|
||||
mini_ob = new LadderMiniOrderBookSettings(mini_ob.create())
|
||||
mini_ob = new LadderMiniOrderBookSettings(mini_ob.get_result())
|
||||
};
|
||||
} else {
|
||||
clone = SparkUtils.clone_proto(_current);
|
||||
#pragma warning disable CS0612 // Type or member is obsolete
|
||||
clone.mini_ob = new LadderMiniOrderBookSettings(_current.mini_ob.value != null ?
|
||||
mini_ob.create().with(sorting_criterion: _current.mini_ob.value.sorting_criterion) :
|
||||
mini_ob.create());
|
||||
mini_ob.get_result().with(sorting_criterion: _current.mini_ob.value.sorting_criterion) :
|
||||
mini_ob.get_result());
|
||||
#pragma warning restore CS0612 // Type or member is obsolete
|
||||
}
|
||||
clone.badge_style = badge_style;
|
||||
@@ -810,4 +811,40 @@ namespace mbts.spark {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class MiniOrderBookGridSettingsVM : GridSettingsVM {
|
||||
public MiniOrderBookGridSettingsVM(IReadOnlyDictionary<string, GridColumnSettingsFeatureFlags> column_flags = null)
|
||||
: base(column_flags) { }
|
||||
|
||||
//public override void sync_params() {
|
||||
// parameters.Clear();
|
||||
// parameters.Add(_font_vm);
|
||||
// parameters.Add(_font_color);
|
||||
// if (_background_enabled)
|
||||
// parameters.Add(_background);
|
||||
// parameters.Add(_selection_highlight);
|
||||
//}
|
||||
}
|
||||
|
||||
//class MiniOrderBookGridSettingsVM : ParameterGroupVM {
|
||||
// public MiniOrderBookGridSettingsVM(GridSettingsVM grid) { }
|
||||
// MiniOrderBookGridSettingsVM(GridSettingsVM grid, ObservableAggregateCollection<object> parameters)
|
||||
// : base("Mini OrderBook", parameters) {
|
||||
// parameters.add_collection(grid.parameters);
|
||||
// }
|
||||
|
||||
// public override void sync_params() {
|
||||
// var fonts_and_colors = HeaderedParameterGroupVM.create("Fonts/Colors", _font_vm, _font_color);
|
||||
// if (_background_enabled)
|
||||
// fonts_and_colors.parameters.Add(_background);
|
||||
// fonts_and_colors.parameters.Add(_selection_highlight);
|
||||
// parameters.SetItems(fonts_and_colors);
|
||||
// }
|
||||
//}
|
||||
|
||||
//class GridSettingsColumnParameterGroupVM : ParameterGroupVM {
|
||||
// public GridSettingsColumnParameterGroupVM(GridSettingsVM grid) : base("Columns") {
|
||||
|
||||
// }
|
||||
//}G
|
||||
}
|
||||
@@ -14,255 +14,177 @@
|
||||
TextElement.FontSize="{x:Static u:FontSizes.Small}"
|
||||
>
|
||||
<UserControl.Resources>
|
||||
<sys:Double x:Key="HorizontalPush">4</sys:Double>
|
||||
<sys:Double x:Key="HorizontalPush2">8</sys:Double>
|
||||
<sys:Double x:Key="HorizontalPush3">12</sys:Double>
|
||||
<sys:Double x:Key="IndentChild">24</sys:Double>
|
||||
<sys:Double x:Key="GroupBoxOffsetLeft">8</sys:Double>
|
||||
<sys:Double x:Key="GroupBoxOffsetRight">2</sys:Double>
|
||||
<sys:Double x:Key="VerticalPush">4</sys:Double>
|
||||
<sys:Double x:Key="VerticalSectionBreak">6</sys:Double>
|
||||
<sys:Double x:Key="VerticalSplit">8</sys:Double>
|
||||
<sys:Double x:Key="CollapsedSizePresetsHeight">24</sys:Double>
|
||||
<!--'Ladder Columns' Column Widths-->
|
||||
<sys:Double x:Key="LadderColumnsShowColumnWidth">57</sys:Double>
|
||||
<sys:Double x:Key="LadderColumnsBackgroundColumnWidth">70</sys:Double>
|
||||
<sys:Double x:Key="LadderColumnsForegroundColumnWidth">81</sys:Double>
|
||||
<sys:Double x:Key="LadderColumnsWidthColumnWidth">50</sys:Double>
|
||||
<sys:Double x:Key="LadderColumnsFontColumnWidth">160</sys:Double>
|
||||
<!--Margins-->
|
||||
<Thickness x:Key="MarginLPush" Left="{StaticResource HorizontalPush}"/>
|
||||
<Thickness x:Key="MarginLPush2" Left="{StaticResource HorizontalPush2}"/>
|
||||
<Thickness x:Key="MarginTPush" Top="{StaticResource HorizontalPush}"/>
|
||||
<Thickness x:Key="MarginTPush2" Top="{StaticResource HorizontalPush2}"/>
|
||||
<Thickness x:Key="MarginIndentChild" Left="{StaticResource IndentChild}"/>
|
||||
<Thickness x:Key="Margin.UP1" Left="{StaticResource HorizontalPush}" Top="{StaticResource VerticalPush}" Right="{StaticResource HorizontalPush}" Bottom="{StaticResource VerticalPush}"/>
|
||||
<!--Colors-->
|
||||
<Color x:Key="LadderColumnDetailsBackgroundColor">LightGray</Color>
|
||||
<Color x:Key="LadderColumnDetailsForegroundColor">Black</Color>
|
||||
<Color x:Key="LadderColumnDetailsBorderColor">Navy</Color>
|
||||
<SolidColorBrush x:Key="LadderColumnDetailsBackgroundBrush" ice:Freeze="True" Color="{StaticResource LadderColumnDetailsBackgroundColor}"/>
|
||||
<SolidColorBrush x:Key="LadderColumnDetailsForegroundBrush" ice:Freeze="True" Color="{StaticResource LadderColumnDetailsForegroundColor}"/>
|
||||
<SolidColorBrush x:Key="LadderColumnDetailsBorderBrush" ice:Freeze="True" Color="{StaticResource LadderColumnDetailsBorderColor}"/>
|
||||
<!--Converters-->
|
||||
<cv:BooleanToVisibilityConverter x:Key="NetChangeGroupBoxIsVisibleVisibilityConverter" VisibilityForTrue="Hidden" VisibilityForFalse="Visible"/>
|
||||
<cv:ValueMap x:Key="ColumnSpanToVisibilityConverter" Default="{u:Visibility Visible}">
|
||||
<cv:KeyValue Key="{u:Int 0}" Value="{u:Visibility Collapsed}"/>
|
||||
</cv:ValueMap>
|
||||
<c:BadgeTextLinesConverter x:Key="BadgeTextLinesConverter"/>
|
||||
<ResourceDictionary>
|
||||
<ResourceDictionary.MergedDictionaries>
|
||||
<usp:GridSettingsResources/>
|
||||
</ResourceDictionary.MergedDictionaries>
|
||||
<sys:Double x:Key="HorizontalPush">4</sys:Double>
|
||||
<sys:Double x:Key="HorizontalPush2">8</sys:Double>
|
||||
<sys:Double x:Key="HorizontalPush3">12</sys:Double>
|
||||
<sys:Double x:Key="IndentChild">24</sys:Double>
|
||||
<sys:Double x:Key="GroupBoxOffsetLeft">8</sys:Double>
|
||||
<sys:Double x:Key="GroupBoxOffsetRight">2</sys:Double>
|
||||
<sys:Double x:Key="VerticalPush">4</sys:Double>
|
||||
<sys:Double x:Key="VerticalSectionBreak">6</sys:Double>
|
||||
<sys:Double x:Key="VerticalSplit">8</sys:Double>
|
||||
<sys:Double x:Key="CollapsedSizePresetsHeight">24</sys:Double>
|
||||
<!--'Ladder Columns' Column Widths-->
|
||||
<sys:Double x:Key="LadderColumnsShowColumnWidth">57</sys:Double>
|
||||
<sys:Double x:Key="LadderColumnsBackgroundColumnWidth">70</sys:Double>
|
||||
<sys:Double x:Key="LadderColumnsForegroundColumnWidth">81</sys:Double>
|
||||
<sys:Double x:Key="LadderColumnsWidthColumnWidth">50</sys:Double>
|
||||
<sys:Double x:Key="LadderColumnsFontColumnWidth">160</sys:Double>
|
||||
<!--Margins-->
|
||||
<Thickness x:Key="MarginLPush" Left="{StaticResource HorizontalPush}"/>
|
||||
<Thickness x:Key="MarginLPush2" Left="{StaticResource HorizontalPush2}"/>
|
||||
<Thickness x:Key="MarginTPush" Top="{StaticResource HorizontalPush}"/>
|
||||
<Thickness x:Key="MarginTPush2" Top="{StaticResource HorizontalPush2}"/>
|
||||
<Thickness x:Key="MarginIndentChild" Left="{StaticResource IndentChild}"/>
|
||||
<Thickness x:Key="Margin.UP1" Left="{StaticResource HorizontalPush}" Top="{StaticResource VerticalPush}" Right="{StaticResource HorizontalPush}" Bottom="{StaticResource VerticalPush}"/>
|
||||
<!--Colors-->
|
||||
<Color x:Key="LadderColumnDetailsBackgroundColor">LightGray</Color>
|
||||
<Color x:Key="LadderColumnDetailsForegroundColor">Black</Color>
|
||||
<Color x:Key="LadderColumnDetailsBorderColor">Navy</Color>
|
||||
<SolidColorBrush x:Key="LadderColumnDetailsBackgroundBrush" ice:Freeze="True" Color="{StaticResource LadderColumnDetailsBackgroundColor}"/>
|
||||
<SolidColorBrush x:Key="LadderColumnDetailsForegroundBrush" ice:Freeze="True" Color="{StaticResource LadderColumnDetailsForegroundColor}"/>
|
||||
<SolidColorBrush x:Key="LadderColumnDetailsBorderBrush" ice:Freeze="True" Color="{StaticResource LadderColumnDetailsBorderColor}"/>
|
||||
<!--Converters-->
|
||||
<cv:BooleanToVisibilityConverter x:Key="NetChangeGroupBoxIsVisibleVisibilityConverter" VisibilityForTrue="Hidden" VisibilityForFalse="Visible"/>
|
||||
<cv:ValueMap x:Key="ColumnSpanToVisibilityConverter" Default="{u:Visibility Visible}">
|
||||
<cv:KeyValue Key="{u:Int 0}" Value="{u:Visibility Collapsed}"/>
|
||||
</cv:ValueMap>
|
||||
<c:BadgeTextLinesConverter x:Key="BadgeTextLinesConverter"/>
|
||||
|
||||
<Style x:Key="ColumnBackground" TargetType="Rectangle">
|
||||
<Setter Property="Grid.RowSpan" Value="3"/>
|
||||
<Setter Property="HorizontalAlignment" Value="Stretch"/>
|
||||
<Setter Property="VerticalAlignment" Value="Stretch"/>
|
||||
</Style>
|
||||
<Style x:Key="VolumeComponentRectangle" TargetType="Rectangle">
|
||||
<Setter Property="VerticalAlignment" Value="Stretch"/>
|
||||
<Setter Property="Width">
|
||||
<Setter.Value>
|
||||
<MultiBinding Converter="{x:Static usp:LadderSettingsView.VolumeBarWidthConverter}">
|
||||
<Binding Path="(usp:LadderSettingsView.VolumeComponentValue)" RelativeSource="{RelativeSource Self}"/>
|
||||
<Binding Path="DataContext.volume.max_total_volume" RelativeSource="{RelativeSource AncestorType=usp:LadderSettingsView}"/>
|
||||
<Binding Path="ActualWidth" RelativeSource="{RelativeSource AncestorType=Canvas}"/>
|
||||
</MultiBinding>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
<Style x:Key="ColumnBackground" TargetType="Rectangle">
|
||||
<Setter Property="Grid.RowSpan" Value="3"/>
|
||||
<Setter Property="HorizontalAlignment" Value="Stretch"/>
|
||||
<Setter Property="VerticalAlignment" Value="Stretch"/>
|
||||
</Style>
|
||||
<Style x:Key="VolumeComponentRectangle" TargetType="Rectangle">
|
||||
<Setter Property="VerticalAlignment" Value="Stretch"/>
|
||||
<Setter Property="Width">
|
||||
<Setter.Value>
|
||||
<MultiBinding Converter="{x:Static usp:LadderSettingsView.VolumeBarWidthConverter}">
|
||||
<Binding Path="(usp:LadderSettingsView.VolumeComponentValue)" RelativeSource="{RelativeSource Self}"/>
|
||||
<Binding Path="DataContext.volume.max_total_volume" RelativeSource="{RelativeSource AncestorType=usp:LadderSettingsView}"/>
|
||||
<Binding Path="ActualWidth" RelativeSource="{RelativeSource AncestorType=Canvas}"/>
|
||||
</MultiBinding>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
|
||||
<Style x:Key="LadderColumnDetailsHyperlink" TargetType="Hyperlink" BasedOn="{StaticResource {x:Type Hyperlink}}">
|
||||
<Setter Property="Foreground" Value="{u:BTSColor header_text}"/>
|
||||
<Setter Property="Command" Value="{Binding toggle_show_details_cmd}"/>
|
||||
<Style.Triggers>
|
||||
<Trigger Property="IsMouseOver" Value="True">
|
||||
<Setter Property="Foreground" Value="Tan"/>
|
||||
</Trigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
<Style x:Key="LadderColumnDetailsHyperlink" TargetType="Hyperlink" BasedOn="{StaticResource {x:Type Hyperlink}}">
|
||||
<Setter Property="Foreground" Value="{u:BTSColor header_text}"/>
|
||||
<Setter Property="Command" Value="{Binding toggle_show_details_cmd}"/>
|
||||
<Style.Triggers>
|
||||
<Trigger Property="IsMouseOver" Value="True">
|
||||
<Setter Property="Foreground" Value="Tan"/>
|
||||
</Trigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
|
||||
<Style x:Key="LadderColumnLadderColumnDetailsBorder" TargetType="Border">
|
||||
<Setter Property="Background" Value="{StaticResource LadderColumnDetailsBackgroundBrush}"/>
|
||||
<Setter Property="BorderBrush" Value="Navy"/>
|
||||
<Setter Property="BorderThickness" Value="1"/>
|
||||
<Setter Property="Padding" Value="12,10,12,6"/>
|
||||
<Setter Property="Visibility" Value="{Binding show_details, Converter={StaticResource BooleanToVisibilityConverter}}"/>
|
||||
</Style>
|
||||
<Style x:Key="LadderColumnLadderColumnDetailsBorder" TargetType="Border">
|
||||
<Setter Property="Background" Value="{StaticResource LadderColumnDetailsBackgroundBrush}"/>
|
||||
<Setter Property="BorderBrush" Value="Navy"/>
|
||||
<Setter Property="BorderThickness" Value="1"/>
|
||||
<Setter Property="Padding" Value="12,10,12,6"/>
|
||||
<Setter Property="Visibility" Value="{Binding show_details, Converter={StaticResource BooleanToVisibilityConverter}}"/>
|
||||
</Style>
|
||||
|
||||
<Style x:Key="HintText" TargetType="TextBlock">
|
||||
<Setter Property="FontStyle" Value="Italic"/>
|
||||
<Setter Property="Foreground" Value="Gray"/>
|
||||
<Setter Property="TextWrapping" Value="Wrap"/>
|
||||
</Style>
|
||||
<Style x:Key="HintText" TargetType="TextBlock">
|
||||
<Setter Property="FontStyle" Value="Italic"/>
|
||||
<Setter Property="Foreground" Value="Gray"/>
|
||||
<Setter Property="TextWrapping" Value="Wrap"/>
|
||||
</Style>
|
||||
|
||||
<Style x:Key="LadderColumnShowToggle" TargetType="CheckBox">
|
||||
<Setter Property="Grid.Column" Value="1"/>
|
||||
<Setter Property="ToolTipService.ShowOnDisabled" Value="True"/>
|
||||
<Setter Property="HorizontalAlignment" Value="Center"/>
|
||||
<Setter Property="VerticalAlignment" Value="Center"/>
|
||||
<Setter Property="IsChecked" Value="{Binding show}"/>
|
||||
<Setter Property="IsEnabled" Value="{Binding can_toggle_show}"/>
|
||||
<Setter Property="ToolTip">
|
||||
<Setter.Value>
|
||||
<MultiBinding Converter="{x:Static usp:LadderSettingsView.LadderColumnToggleShowToolTipConverter}">
|
||||
<Binding Path="can_toggle_show"/>
|
||||
<Binding Path="show"/>
|
||||
<Binding Path="name"/>
|
||||
</MultiBinding>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
<Style x:Key="LadderColumnShowToggle" TargetType="CheckBox">
|
||||
<Setter Property="Grid.Column" Value="1"/>
|
||||
<Setter Property="ToolTipService.ShowOnDisabled" Value="True"/>
|
||||
<Setter Property="HorizontalAlignment" Value="Center"/>
|
||||
<Setter Property="VerticalAlignment" Value="Center"/>
|
||||
<Setter Property="IsChecked" Value="{Binding show}"/>
|
||||
<Setter Property="IsEnabled" Value="{Binding can_toggle_show}"/>
|
||||
<Setter Property="ToolTip">
|
||||
<Setter.Value>
|
||||
<MultiBinding Converter="{x:Static usp:LadderSettingsView.LadderColumnToggleShowToolTipConverter}">
|
||||
<Binding Path="can_toggle_show"/>
|
||||
<Binding Path="show"/>
|
||||
<Binding Path="name"/>
|
||||
</MultiBinding>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
|
||||
<Style x:Key="LadderColumnColorPicker" TargetType="c:ColorPickerPopup">
|
||||
<Setter Property="BorderBrush" Value="Transparent"/>
|
||||
<Setter Property="BorderThickness" Value="0"/>
|
||||
<Setter Property="PopupPlacement" Value="Left"/>
|
||||
</Style>
|
||||
<Style x:Key="LadderColumnBackgroundPicker" TargetType="c:ColorPickerPopup" BasedOn="{StaticResource LadderColumnColorPicker}">
|
||||
<Setter Property="Grid.Column" Value="2"/>
|
||||
<Setter Property="Palettes" Value="{Binding background_palettes}"/>
|
||||
<Setter Property="SelectedColor" Value="{Binding background}"/>
|
||||
</Style>
|
||||
<Style x:Key="LadderColumnForegroundPicker" TargetType="c:ColorPickerPopup" BasedOn="{StaticResource LadderColumnColorPicker}">
|
||||
<Setter Property="Grid.Column" Value="3"/>
|
||||
<Setter Property="Palettes" Value="{Binding foreground_palettes}"/>
|
||||
<Setter Property="SelectedColor" Value="{Binding foreground}"/>
|
||||
</Style>
|
||||
<Style x:Key="LadderColumnWidthTickUpDown" TargetType="c:TickUpDownUInt">
|
||||
<Setter Property="Grid.Column" Value="4"/>
|
||||
<Setter Property="Value" Value="{Binding width}"/>
|
||||
</Style>
|
||||
<Style x:Key="LadderColumnColorPicker" TargetType="c:ColorPickerPopup">
|
||||
<Setter Property="BorderBrush" Value="Transparent"/>
|
||||
<Setter Property="BorderThickness" Value="0"/>
|
||||
<Setter Property="PopupPlacement" Value="Left"/>
|
||||
</Style>
|
||||
<Style x:Key="LadderColumnBackgroundPicker" TargetType="c:ColorPickerPopup" BasedOn="{StaticResource LadderColumnColorPicker}">
|
||||
<Setter Property="Grid.Column" Value="2"/>
|
||||
<Setter Property="Palettes" Value="{Binding background_palettes}"/>
|
||||
<Setter Property="SelectedColor" Value="{Binding background}"/>
|
||||
</Style>
|
||||
<Style x:Key="LadderColumnForegroundPicker" TargetType="c:ColorPickerPopup" BasedOn="{StaticResource LadderColumnColorPicker}">
|
||||
<Setter Property="Grid.Column" Value="3"/>
|
||||
<Setter Property="Palettes" Value="{Binding foreground_palettes}"/>
|
||||
<Setter Property="SelectedColor" Value="{Binding foreground}"/>
|
||||
</Style>
|
||||
<Style x:Key="LadderColumnWidthTickUpDown" TargetType="c:TickUpDownUInt">
|
||||
<Setter Property="Grid.Column" Value="4"/>
|
||||
<Setter Property="Value" Value="{Binding width}"/>
|
||||
</Style>
|
||||
|
||||
<Style x:Key="LadderColumnFontPicker" TargetType="c:PopupControl">
|
||||
<Setter Property="Grid.Column" Value="5"/>
|
||||
<Setter Property="BorderBrush" Value="Transparent"/>
|
||||
<Setter Property="BorderThickness" Value="0"/>
|
||||
<Setter Property="PopupPlacement" Value="Left"/>
|
||||
<Setter Property="ShowExpanderArrow" Value="False"/>
|
||||
<Setter Property="Content" Value="{Binding}"/>
|
||||
<Setter Property="ContentTemplate">
|
||||
<Setter.Value>
|
||||
<DataTemplate>
|
||||
<c:FontPicker SelectedFontSize="{Binding font_size}" Typeface="{Binding typeface}"/>
|
||||
</DataTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
<Setter Property="Header" Value="{Binding}"/>
|
||||
<Setter Property="HeaderTemplate">
|
||||
<Setter.Value>
|
||||
<DataTemplate>
|
||||
<TextBlock
|
||||
FontFamily="{Binding font_family}"
|
||||
FontSize="{Binding font_size}"
|
||||
FontWeight="{Binding font_weight}"
|
||||
Margin="4,0"
|
||||
Text="{Binding font_display_name}"
|
||||
/>
|
||||
</DataTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
|
||||
<Style x:Key="LadderColumnSample" TargetType="Border">
|
||||
<Setter Property="Grid.Column" Value="6"/>
|
||||
<Setter Property="Background" Value="{Binding background, Converter={StaticResource ColorToBrushConverter}}"/>
|
||||
<Setter Property="BorderBrush" Value="Black"/>
|
||||
<Setter Property="BorderThickness" Value="2,0,0,0"/>
|
||||
<Setter Property="Padding" Value="4,0"/>
|
||||
</Style>
|
||||
|
||||
<Style x:Key="LadderColumnRowSeparator" TargetType="Rectangle">
|
||||
<Setter Property="Grid.Column" Value="1"/>
|
||||
<Setter Property="Grid.ColumnSpan" Value="6"/>
|
||||
<Setter Property="Fill" Value="DimGray"/>
|
||||
<Setter Property="Height" Value="1"/>
|
||||
<Setter Property="HorizontalAlignment" Value="Stretch"/>
|
||||
<Setter Property="VerticalAlignment" Value="Bottom"/>
|
||||
</Style>
|
||||
|
||||
<u:KeyedTemplateSelector x:Key="LadderColumnTemplateSelector">
|
||||
<u:KeyedTemplateSelector.Default>
|
||||
<DataTemplate>
|
||||
<Grid Height="{Binding DataContext.row_height, Mode=OneWay, RelativeSource={RelativeSource AncestorType=usp:LadderSettingsView}}">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" SharedSizeGroup="c0"/>
|
||||
<ColumnDefinition Width="{Binding Source={StaticResource LadderColumnsShowColumnWidth}}" SharedSizeGroup="c1"/>
|
||||
<ColumnDefinition Width="{Binding Source={StaticResource LadderColumnsBackgroundColumnWidth}}" SharedSizeGroup="c2"/>
|
||||
<ColumnDefinition Width="{Binding Source={StaticResource LadderColumnsForegroundColumnWidth}}" SharedSizeGroup="c3"/>
|
||||
<ColumnDefinition Width="{Binding Source={StaticResource LadderColumnsWidthColumnWidth}}" SharedSizeGroup="c4"/>
|
||||
<ColumnDefinition Width="{Binding Source={StaticResource LadderColumnsFontColumnWidth}}" SharedSizeGroup="c5"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<Border Grid.Column="0" Background="{u:BTSColor header}" Padding="6,3">
|
||||
<TextBlock VerticalAlignment="Center" Text="{Binding name}" Foreground="{u:BTSColor header_text}"/>
|
||||
</Border>
|
||||
<CheckBox Style="{StaticResource LadderColumnShowToggle}"/>
|
||||
<c:ColorPickerPopup Style="{StaticResource LadderColumnBackgroundPicker}"/>
|
||||
<c:ColorPickerPopup Style="{StaticResource LadderColumnForegroundPicker}"/>
|
||||
<c:TickUpDownUInt Style="{StaticResource LadderColumnWidthTickUpDown}"/>
|
||||
<c:PopupControl Style="{StaticResource LadderColumnFontPicker}"/>
|
||||
<Border Style="{StaticResource LadderColumnSample}">
|
||||
<Style x:Key="LadderColumnFontPicker" TargetType="c:PopupControl">
|
||||
<Setter Property="Grid.Column" Value="5"/>
|
||||
<Setter Property="BorderBrush" Value="Transparent"/>
|
||||
<Setter Property="BorderThickness" Value="0"/>
|
||||
<Setter Property="PopupPlacement" Value="Left"/>
|
||||
<Setter Property="ShowExpanderArrow" Value="False"/>
|
||||
<Setter Property="Content" Value="{Binding}"/>
|
||||
<Setter Property="ContentTemplate">
|
||||
<Setter.Value>
|
||||
<DataTemplate>
|
||||
<c:FontPicker SelectedFontSize="{Binding font_size}" Typeface="{Binding typeface}"/>
|
||||
</DataTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
<Setter Property="Header" Value="{Binding}"/>
|
||||
<Setter Property="HeaderTemplate">
|
||||
<Setter.Value>
|
||||
<DataTemplate>
|
||||
<TextBlock
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
FontFamily="{Binding font_family}"
|
||||
FontSize="{Binding font_size}"
|
||||
FontWeight="{Binding font_weight}"
|
||||
Foreground="{Binding foreground, Converter={StaticResource ColorToBrushConverter}}"
|
||||
Text="{Binding sample_text}"
|
||||
Margin="4,0"
|
||||
Text="{Binding font_display_name}"
|
||||
/>
|
||||
</Border>
|
||||
<Rectangle Style="{StaticResource LadderColumnRowSeparator}"/>
|
||||
</Grid>
|
||||
</DataTemplate>
|
||||
</u:KeyedTemplateSelector.Default>
|
||||
<!--Orders-->
|
||||
<u:TemplateSelectorKeyValue Key="{x:Static usp:LadderSettingsView.OrdersColumnTemplateKey}">
|
||||
<DataTemplate>
|
||||
<Grid Height="{Binding DataContext.row_height, Mode=OneWay, RelativeSource={RelativeSource AncestorType=usp:LadderSettingsView}}">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" SharedSizeGroup="c0"/>
|
||||
<ColumnDefinition Width="{Binding Source={StaticResource LadderColumnsShowColumnWidth}}" SharedSizeGroup="c1"/>
|
||||
<ColumnDefinition Width="{Binding Source={StaticResource LadderColumnsBackgroundColumnWidth}}" SharedSizeGroup="c2"/>
|
||||
<ColumnDefinition Width="{Binding Source={StaticResource LadderColumnsForegroundColumnWidth}}" SharedSizeGroup="c3"/>
|
||||
<ColumnDefinition Width="{Binding Source={StaticResource LadderColumnsWidthColumnWidth}}" SharedSizeGroup="c4"/>
|
||||
<ColumnDefinition Width="{Binding Source={StaticResource LadderColumnsFontColumnWidth}}" SharedSizeGroup="c5"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<Border Grid.Column="0" Background="{u:BTSColor header}" Padding="6,3">
|
||||
<TextBlock VerticalAlignment="Center" Text="{Binding name}" Foreground="{u:BTSColor header_text}"/>
|
||||
</Border>
|
||||
<CheckBox Style="{StaticResource LadderColumnShowToggle}"/>
|
||||
<c:ColorPickerPopup Style="{StaticResource LadderColumnBackgroundPicker}"/>
|
||||
<c:ColorPickerPopup Style="{StaticResource LadderColumnForegroundPicker}"/>
|
||||
<c:TickUpDownUInt Style="{StaticResource LadderColumnWidthTickUpDown}"/>
|
||||
<c:PopupControl Style="{StaticResource LadderColumnFontPicker}"/>
|
||||
<Border Style="{StaticResource LadderColumnSample}">
|
||||
<c:Badge
|
||||
Background="{Binding badge_background}"
|
||||
BorderBrush="{Binding badge_border}"
|
||||
FontFamily="{Binding font_family}"
|
||||
FontSize="{Binding font_size}"
|
||||
FontWeight="{Binding font_weight}"
|
||||
Foreground="{Binding foreground, Converter={StaticResource ColorToBrushConverter}}"
|
||||
Width="{x:Static c:Badge.DefaultWidth}"
|
||||
>
|
||||
<c:Badge.TextLines>
|
||||
<MultiBinding Converter="{StaticResource BadgeTextLinesConverter}">
|
||||
<Binding Path="sample_side"/>
|
||||
<Binding Path="sample_quantity"/>
|
||||
<Binding Path="sample_filled"/>
|
||||
<Binding Path="DataContext.badge_style" RelativeSource="{RelativeSource AncestorType=usp:LadderSettingsView}"/>
|
||||
</MultiBinding>
|
||||
</c:Badge.TextLines>
|
||||
</c:Badge>
|
||||
</Border>
|
||||
<Rectangle Style="{StaticResource LadderColumnRowSeparator}"/>
|
||||
</Grid>
|
||||
</DataTemplate>
|
||||
</u:TemplateSelectorKeyValue>
|
||||
<!--Last-->
|
||||
<u:TemplateSelectorKeyValue Key="{x:Static usp:LadderSettingsView.LastColumnTemplateKey}">
|
||||
<DataTemplate>
|
||||
<StackPanel>
|
||||
</DataTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
|
||||
<Style x:Key="LadderColumnSample" TargetType="Border">
|
||||
<Setter Property="Grid.Column" Value="6"/>
|
||||
<Setter Property="Background" Value="{Binding background, Converter={StaticResource ColorToBrushConverter}}"/>
|
||||
<Setter Property="BorderBrush" Value="Black"/>
|
||||
<Setter Property="BorderThickness" Value="2,0,0,0"/>
|
||||
<Setter Property="Padding" Value="4,0"/>
|
||||
</Style>
|
||||
|
||||
<Style x:Key="LadderColumnRowSeparator" TargetType="Rectangle">
|
||||
<Setter Property="Grid.Column" Value="1"/>
|
||||
<Setter Property="Grid.ColumnSpan" Value="6"/>
|
||||
<Setter Property="Fill" Value="DimGray"/>
|
||||
<Setter Property="Height" Value="1"/>
|
||||
<Setter Property="HorizontalAlignment" Value="Stretch"/>
|
||||
<Setter Property="VerticalAlignment" Value="Bottom"/>
|
||||
</Style>
|
||||
|
||||
<u:KeyedTemplateSelector x:Key="LadderColumnTemplateSelector">
|
||||
<u:KeyedTemplateSelector.Default>
|
||||
<DataTemplate>
|
||||
<Grid Height="{Binding DataContext.row_height, Mode=OneWay, RelativeSource={RelativeSource AncestorType=usp:LadderSettingsView}}">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" SharedSizeGroup="c0"/>
|
||||
@@ -271,21 +193,10 @@
|
||||
<ColumnDefinition Width="{Binding Source={StaticResource LadderColumnsForegroundColumnWidth}}" SharedSizeGroup="c3"/>
|
||||
<ColumnDefinition Width="{Binding Source={StaticResource LadderColumnsWidthColumnWidth}}" SharedSizeGroup="c4"/>
|
||||
<ColumnDefinition Width="{Binding Source={StaticResource LadderColumnsFontColumnWidth}}" SharedSizeGroup="c5"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<Border Grid.Column="0" Background="{u:BTSColor header}" Padding="6,3">
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" SharedSizeGroup="name_col"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBlock VerticalAlignment="Center" Text="{Binding name}" Foreground="{u:BTSColor header_text}"/>
|
||||
<TextBlock Grid.Column="1" Margin="4,0,2,0">
|
||||
<Hyperlink Style="{StaticResource LadderColumnDetailsHyperlink}">
|
||||
(Advanced)
|
||||
</Hyperlink>
|
||||
</TextBlock>
|
||||
</Grid>
|
||||
<TextBlock VerticalAlignment="Center" Text="{Binding name}" Foreground="{u:BTSColor header_text}"/>
|
||||
</Border>
|
||||
<CheckBox Style="{StaticResource LadderColumnShowToggle}"/>
|
||||
<c:ColorPickerPopup Style="{StaticResource LadderColumnBackgroundPicker}"/>
|
||||
@@ -305,21 +216,11 @@
|
||||
</Border>
|
||||
<Rectangle Style="{StaticResource LadderColumnRowSeparator}"/>
|
||||
</Grid>
|
||||
<Border Style="{StaticResource LadderColumnLadderColumnDetailsBorder}">
|
||||
<CheckBox
|
||||
Content="Show Last on Volume Column"
|
||||
DataContext="{Binding DataContext, RelativeSource={RelativeSource AncestorType=usp:LadderSettingsView}}"
|
||||
IsChecked="{Binding volume.show_last_trade}"
|
||||
Visibility="{Binding last_column.show_details, Converter={StaticResource BooleanToVisibilityConverter}}"
|
||||
/>
|
||||
</Border>
|
||||
</StackPanel>
|
||||
</DataTemplate>
|
||||
</u:TemplateSelectorKeyValue>
|
||||
<!--Volume-->
|
||||
<u:TemplateSelectorKeyValue Key="{x:Static usp:LadderSettingsView.VolumeColumnTemplateKey}">
|
||||
<DataTemplate>
|
||||
<StackPanel>
|
||||
</DataTemplate>
|
||||
</u:KeyedTemplateSelector.Default>
|
||||
<!--Orders-->
|
||||
<u:TemplateSelectorKeyValue Key="{x:Static usp:LadderSettingsView.OrdersColumnTemplateKey}">
|
||||
<DataTemplate>
|
||||
<Grid Height="{Binding DataContext.row_height, Mode=OneWay, RelativeSource={RelativeSource AncestorType=usp:LadderSettingsView}}">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" SharedSizeGroup="c0"/>
|
||||
@@ -331,18 +232,7 @@
|
||||
<ColumnDefinition Width="*"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<Border Grid.Column="0" Background="{u:BTSColor header}" Padding="6,3">
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" SharedSizeGroup="name_col"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBlock VerticalAlignment="Center" Text="{Binding name}" Foreground="{u:BTSColor header_text}"/>
|
||||
<TextBlock Grid.Column="1" Margin="4,0,2,0">
|
||||
<Hyperlink Style="{StaticResource LadderColumnDetailsHyperlink}">
|
||||
(Advanced)
|
||||
</Hyperlink>
|
||||
</TextBlock>
|
||||
</Grid>
|
||||
<TextBlock VerticalAlignment="Center" Text="{Binding name}" Foreground="{u:BTSColor header_text}"/>
|
||||
</Border>
|
||||
<CheckBox Style="{StaticResource LadderColumnShowToggle}"/>
|
||||
<c:ColorPickerPopup Style="{StaticResource LadderColumnBackgroundPicker}"/>
|
||||
@@ -350,114 +240,223 @@
|
||||
<c:TickUpDownUInt Style="{StaticResource LadderColumnWidthTickUpDown}"/>
|
||||
<c:PopupControl Style="{StaticResource LadderColumnFontPicker}"/>
|
||||
<Border Style="{StaticResource LadderColumnSample}">
|
||||
<Canvas DataContext="{Binding DataContext.volume, RelativeSource={RelativeSource AncestorType=usp:LadderSettingsView}}" HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
|
||||
<Grid Height="{Binding ActualHeight, RelativeSource={RelativeSource AncestorType=Canvas}}">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<Rectangle
|
||||
Grid.ColumnSpan="4"
|
||||
usp:LadderSettingsView.VolumeComponentValue="{Binding total.sample_data.total}"
|
||||
Fill="{Binding total.bars.value, Converter={StaticResource ColorToBrushConverter}}"
|
||||
Visibility="{Binding total.bars.enabled, Converter={StaticResource BooleanToVisibilityConverter}}"
|
||||
Style="{StaticResource VolumeComponentRectangle}"
|
||||
/>
|
||||
<Rectangle
|
||||
usp:LadderSettingsView.VolumeComponentValue="{Binding buy.sample_data.buy}"
|
||||
Fill="{Binding buy.bars.value, Converter={StaticResource ColorToBrushConverter}}"
|
||||
Visibility="{Binding buy.bars.enabled, Converter={StaticResource BooleanToVisibilityConverter}}"
|
||||
Style="{StaticResource VolumeComponentRectangle}"
|
||||
/>
|
||||
<Rectangle
|
||||
Grid.Column="1"
|
||||
usp:LadderSettingsView.VolumeComponentValue="{Binding sell.sample_data.sell}"
|
||||
Fill="{Binding sell.bars.value, Converter={StaticResource ColorToBrushConverter}}"
|
||||
Visibility="{Binding sell.bars.enabled, Converter={StaticResource BooleanToVisibilityConverter}}"
|
||||
Style="{StaticResource VolumeComponentRectangle}"
|
||||
/>
|
||||
<Rectangle
|
||||
Grid.Column="2"
|
||||
usp:LadderSettingsView.VolumeComponentValue="{Binding passive.sample_data.passive}"
|
||||
Fill="{Binding passive.bars.value, Converter={StaticResource ColorToBrushConverter}}"
|
||||
Visibility="{Binding passive.bars.enabled, Converter={StaticResource BooleanToVisibilityConverter}}"
|
||||
Style="{StaticResource VolumeComponentRectangle}"
|
||||
/>
|
||||
</Grid>
|
||||
<Border Width="{Binding ActualWidth, RelativeSource={RelativeSource AncestorType=Canvas}}" Height="{Binding ActualHeight, RelativeSource={RelativeSource AncestorType=Canvas}}">
|
||||
<TextBlock
|
||||
Grid.ColumnSpan="4"
|
||||
HorizontalAlignment="Center"
|
||||
Text="{Binding passive.sample_data.numbers_text}"
|
||||
Foreground="{Binding general.foreground, Converter={StaticResource ColorToBrushConverter}}"
|
||||
/>
|
||||
</Border>
|
||||
</Canvas>
|
||||
<c:Badge
|
||||
Background="{Binding badge_background}"
|
||||
BorderBrush="{Binding badge_border}"
|
||||
FontFamily="{Binding font_family}"
|
||||
FontSize="{Binding font_size}"
|
||||
FontWeight="{Binding font_weight}"
|
||||
Foreground="{Binding foreground, Converter={StaticResource ColorToBrushConverter}}"
|
||||
Width="{x:Static c:Badge.DefaultWidth}"
|
||||
>
|
||||
<c:Badge.TextLines>
|
||||
<MultiBinding Converter="{StaticResource BadgeTextLinesConverter}">
|
||||
<Binding Path="sample_side"/>
|
||||
<Binding Path="sample_quantity"/>
|
||||
<Binding Path="sample_filled"/>
|
||||
<Binding Path="DataContext.badge_style" RelativeSource="{RelativeSource AncestorType=usp:LadderSettingsView}"/>
|
||||
</MultiBinding>
|
||||
</c:Badge.TextLines>
|
||||
</c:Badge>
|
||||
</Border>
|
||||
<Rectangle Style="{StaticResource LadderColumnRowSeparator}"/>
|
||||
</Grid>
|
||||
<Border Background="{StaticResource LadderColumnDetailsBackgroundBrush}" BorderBrush="{StaticResource LadderColumnDetailsBorderBrush}" BorderThickness="1" Visibility="{Binding show_details, Converter={StaticResource BooleanToVisibilityConverter}}">
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="27"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
</Grid.RowDefinitions>
|
||||
<Grid x:Name="volume_headers" TextElement.Foreground="{StaticResource LadderColumnDetailsForegroundBrush}">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition SharedSizeGroup="c0"/>
|
||||
<ColumnDefinition Width="Auto" SharedSizeGroup="c1"/>
|
||||
<ColumnDefinition Width="Auto" SharedSizeGroup="c2"/>
|
||||
<ColumnDefinition SharedSizeGroup="c3"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBlock Grid.Column="1" HorizontalAlignment="Center" Margin="6,0" Text="Show Bar" VerticalAlignment="Center"/>
|
||||
<TextBlock Grid.Column="2" HorizontalAlignment="Center" Margin="6,0" Text="Bar Color" VerticalAlignment="Center"/>
|
||||
<TextBlock Grid.Column="3" HorizontalAlignment="Center" Margin="6,0" Text="Show Number" VerticalAlignment="Center"/>
|
||||
</Grid>
|
||||
<ItemsControl x:Name="volume_components_items_source" Grid.Row="1" Margin="0,0,0,8" DataContext="{Binding DataContext.volume, RelativeSource={RelativeSource AncestorType=usp:LadderSettingsView}}" ItemsSource="{Binding volume_components}">
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<Grid Height="{Binding DataContext.row_height, Mode=OneWay, RelativeSource={RelativeSource AncestorType=usp:LadderSettingsView}}" IsEnabled="{Binding show}">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" SharedSizeGroup="c0"/>
|
||||
<ColumnDefinition SharedSizeGroup="c1"/>
|
||||
<ColumnDefinition SharedSizeGroup="c2"/>
|
||||
<ColumnDefinition SharedSizeGroup="c3"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<Border Grid.Column="1" Grid.ColumnSpan="5" Background="White" BorderBrush="{StaticResource LadderColumnDetailsBorderBrush}" BorderThickness="1" Margin="0,-1" IsHitTestVisible="False"/>
|
||||
<TextBlock Margin="70,0,0,0" HorizontalAlignment="Left" VerticalAlignment="Center" Foreground="{StaticResource LadderColumnDetailsForegroundBrush}" Text="{Binding name}"/>
|
||||
<CheckBox Grid.Column="1" HorizontalAlignment="Center" VerticalAlignment="Center" IsChecked="{Binding bars.enabled}"/>
|
||||
<c:ColorPickerPopup
|
||||
Grid.Column="2"
|
||||
BorderBrush="Transparent"
|
||||
BorderThickness="0"
|
||||
PopupPlacement="Left"
|
||||
Palettes="{Binding palettes}"
|
||||
SelectedColor="{Binding bars.value}"
|
||||
/>
|
||||
<CheckBox Grid.Column="3" HorizontalAlignment="Center" VerticalAlignment="Center" IsChecked="{Binding show_numbers}"/>
|
||||
</Grid>
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
</ItemsControl>
|
||||
</DataTemplate>
|
||||
</u:TemplateSelectorKeyValue>
|
||||
<!--Last-->
|
||||
<u:TemplateSelectorKeyValue Key="{x:Static usp:LadderSettingsView.LastColumnTemplateKey}">
|
||||
<DataTemplate>
|
||||
<StackPanel>
|
||||
<Grid Height="{Binding DataContext.row_height, Mode=OneWay, RelativeSource={RelativeSource AncestorType=usp:LadderSettingsView}}">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" SharedSizeGroup="c0"/>
|
||||
<ColumnDefinition Width="{Binding Source={StaticResource LadderColumnsShowColumnWidth}}" SharedSizeGroup="c1"/>
|
||||
<ColumnDefinition Width="{Binding Source={StaticResource LadderColumnsBackgroundColumnWidth}}" SharedSizeGroup="c2"/>
|
||||
<ColumnDefinition Width="{Binding Source={StaticResource LadderColumnsForegroundColumnWidth}}" SharedSizeGroup="c3"/>
|
||||
<ColumnDefinition Width="{Binding Source={StaticResource LadderColumnsWidthColumnWidth}}" SharedSizeGroup="c4"/>
|
||||
<ColumnDefinition Width="{Binding Source={StaticResource LadderColumnsFontColumnWidth}}" SharedSizeGroup="c5"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<Border Grid.Column="0" Background="{u:BTSColor header}" Padding="6,3">
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" SharedSizeGroup="name_col"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBlock VerticalAlignment="Center" Text="{Binding name}" Foreground="{u:BTSColor header_text}"/>
|
||||
<TextBlock Grid.Column="1" Margin="4,0,2,0">
|
||||
<Hyperlink Style="{StaticResource LadderColumnDetailsHyperlink}">
|
||||
(Advanced)
|
||||
</Hyperlink>
|
||||
</TextBlock>
|
||||
</Grid>
|
||||
</Border>
|
||||
<CheckBox Style="{StaticResource LadderColumnShowToggle}"/>
|
||||
<c:ColorPickerPopup Style="{StaticResource LadderColumnBackgroundPicker}"/>
|
||||
<c:ColorPickerPopup Style="{StaticResource LadderColumnForegroundPicker}"/>
|
||||
<c:TickUpDownUInt Style="{StaticResource LadderColumnWidthTickUpDown}"/>
|
||||
<c:PopupControl Style="{StaticResource LadderColumnFontPicker}"/>
|
||||
<Border Style="{StaticResource LadderColumnSample}">
|
||||
<TextBlock
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
FontFamily="{Binding font_family}"
|
||||
FontSize="{Binding font_size}"
|
||||
FontWeight="{Binding font_weight}"
|
||||
Foreground="{Binding foreground, Converter={StaticResource ColorToBrushConverter}}"
|
||||
Text="{Binding sample_text}"
|
||||
/>
|
||||
</Border>
|
||||
<Rectangle Style="{StaticResource LadderColumnRowSeparator}"/>
|
||||
</Grid>
|
||||
</Border>
|
||||
</StackPanel>
|
||||
</DataTemplate>
|
||||
</u:TemplateSelectorKeyValue>
|
||||
</u:KeyedTemplateSelector>
|
||||
<Border Style="{StaticResource LadderColumnLadderColumnDetailsBorder}">
|
||||
<CheckBox
|
||||
Content="Show Last on Volume Column"
|
||||
DataContext="{Binding DataContext, RelativeSource={RelativeSource AncestorType=usp:LadderSettingsView}}"
|
||||
IsChecked="{Binding volume.show_last_trade}"
|
||||
Visibility="{Binding last_column.show_details, Converter={StaticResource BooleanToVisibilityConverter}}"
|
||||
/>
|
||||
</Border>
|
||||
</StackPanel>
|
||||
</DataTemplate>
|
||||
</u:TemplateSelectorKeyValue>
|
||||
<!--Volume-->
|
||||
<u:TemplateSelectorKeyValue Key="{x:Static usp:LadderSettingsView.VolumeColumnTemplateKey}">
|
||||
<DataTemplate>
|
||||
<StackPanel>
|
||||
<Grid Height="{Binding DataContext.row_height, Mode=OneWay, RelativeSource={RelativeSource AncestorType=usp:LadderSettingsView}}">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" SharedSizeGroup="c0"/>
|
||||
<ColumnDefinition Width="{Binding Source={StaticResource LadderColumnsShowColumnWidth}}" SharedSizeGroup="c1"/>
|
||||
<ColumnDefinition Width="{Binding Source={StaticResource LadderColumnsBackgroundColumnWidth}}" SharedSizeGroup="c2"/>
|
||||
<ColumnDefinition Width="{Binding Source={StaticResource LadderColumnsForegroundColumnWidth}}" SharedSizeGroup="c3"/>
|
||||
<ColumnDefinition Width="{Binding Source={StaticResource LadderColumnsWidthColumnWidth}}" SharedSizeGroup="c4"/>
|
||||
<ColumnDefinition Width="{Binding Source={StaticResource LadderColumnsFontColumnWidth}}" SharedSizeGroup="c5"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<Border Grid.Column="0" Background="{u:BTSColor header}" Padding="6,3">
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" SharedSizeGroup="name_col"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBlock VerticalAlignment="Center" Text="{Binding name}" Foreground="{u:BTSColor header_text}"/>
|
||||
<TextBlock Grid.Column="1" Margin="4,0,2,0">
|
||||
<Hyperlink Style="{StaticResource LadderColumnDetailsHyperlink}">
|
||||
(Advanced)
|
||||
</Hyperlink>
|
||||
</TextBlock>
|
||||
</Grid>
|
||||
</Border>
|
||||
<CheckBox Style="{StaticResource LadderColumnShowToggle}"/>
|
||||
<c:ColorPickerPopup Style="{StaticResource LadderColumnBackgroundPicker}"/>
|
||||
<c:ColorPickerPopup Style="{StaticResource LadderColumnForegroundPicker}"/>
|
||||
<c:TickUpDownUInt Style="{StaticResource LadderColumnWidthTickUpDown}"/>
|
||||
<c:PopupControl Style="{StaticResource LadderColumnFontPicker}"/>
|
||||
<Border Style="{StaticResource LadderColumnSample}">
|
||||
<Canvas DataContext="{Binding DataContext.volume, RelativeSource={RelativeSource AncestorType=usp:LadderSettingsView}}" HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
|
||||
<Grid Height="{Binding ActualHeight, RelativeSource={RelativeSource AncestorType=Canvas}}">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<Rectangle
|
||||
Grid.ColumnSpan="4"
|
||||
usp:LadderSettingsView.VolumeComponentValue="{Binding total.sample_data.total}"
|
||||
Fill="{Binding total.bars.value, Converter={StaticResource ColorToBrushConverter}}"
|
||||
Visibility="{Binding total.bars.enabled, Converter={StaticResource BooleanToVisibilityConverter}}"
|
||||
Style="{StaticResource VolumeComponentRectangle}"
|
||||
/>
|
||||
<Rectangle
|
||||
usp:LadderSettingsView.VolumeComponentValue="{Binding buy.sample_data.buy}"
|
||||
Fill="{Binding buy.bars.value, Converter={StaticResource ColorToBrushConverter}}"
|
||||
Visibility="{Binding buy.bars.enabled, Converter={StaticResource BooleanToVisibilityConverter}}"
|
||||
Style="{StaticResource VolumeComponentRectangle}"
|
||||
/>
|
||||
<Rectangle
|
||||
Grid.Column="1"
|
||||
usp:LadderSettingsView.VolumeComponentValue="{Binding sell.sample_data.sell}"
|
||||
Fill="{Binding sell.bars.value, Converter={StaticResource ColorToBrushConverter}}"
|
||||
Visibility="{Binding sell.bars.enabled, Converter={StaticResource BooleanToVisibilityConverter}}"
|
||||
Style="{StaticResource VolumeComponentRectangle}"
|
||||
/>
|
||||
<Rectangle
|
||||
Grid.Column="2"
|
||||
usp:LadderSettingsView.VolumeComponentValue="{Binding passive.sample_data.passive}"
|
||||
Fill="{Binding passive.bars.value, Converter={StaticResource ColorToBrushConverter}}"
|
||||
Visibility="{Binding passive.bars.enabled, Converter={StaticResource BooleanToVisibilityConverter}}"
|
||||
Style="{StaticResource VolumeComponentRectangle}"
|
||||
/>
|
||||
</Grid>
|
||||
<Border Width="{Binding ActualWidth, RelativeSource={RelativeSource AncestorType=Canvas}}" Height="{Binding ActualHeight, RelativeSource={RelativeSource AncestorType=Canvas}}">
|
||||
<TextBlock
|
||||
Grid.ColumnSpan="4"
|
||||
HorizontalAlignment="Center"
|
||||
Text="{Binding passive.sample_data.numbers_text}"
|
||||
Foreground="{Binding general.foreground, Converter={StaticResource ColorToBrushConverter}}"
|
||||
/>
|
||||
</Border>
|
||||
</Canvas>
|
||||
</Border>
|
||||
<Rectangle Style="{StaticResource LadderColumnRowSeparator}"/>
|
||||
</Grid>
|
||||
<Border Background="{StaticResource LadderColumnDetailsBackgroundBrush}" BorderBrush="{StaticResource LadderColumnDetailsBorderBrush}" BorderThickness="1" Visibility="{Binding show_details, Converter={StaticResource BooleanToVisibilityConverter}}">
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="27"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
</Grid.RowDefinitions>
|
||||
<Grid x:Name="volume_headers" TextElement.Foreground="{StaticResource LadderColumnDetailsForegroundBrush}">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition SharedSizeGroup="c0"/>
|
||||
<ColumnDefinition Width="Auto" SharedSizeGroup="c1"/>
|
||||
<ColumnDefinition Width="Auto" SharedSizeGroup="c2"/>
|
||||
<ColumnDefinition SharedSizeGroup="c3"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBlock Grid.Column="1" HorizontalAlignment="Center" Margin="6,0" Text="Show Bar" VerticalAlignment="Center"/>
|
||||
<TextBlock Grid.Column="2" HorizontalAlignment="Center" Margin="6,0" Text="Bar Color" VerticalAlignment="Center"/>
|
||||
<TextBlock Grid.Column="3" HorizontalAlignment="Center" Margin="6,0" Text="Show Number" VerticalAlignment="Center"/>
|
||||
</Grid>
|
||||
<ItemsControl x:Name="volume_components_items_source" Grid.Row="1" Margin="0,0,0,8" DataContext="{Binding DataContext.volume, RelativeSource={RelativeSource AncestorType=usp:LadderSettingsView}}" ItemsSource="{Binding volume_components}">
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<Grid Height="{Binding DataContext.row_height, Mode=OneWay, RelativeSource={RelativeSource AncestorType=usp:LadderSettingsView}}" IsEnabled="{Binding show}">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" SharedSizeGroup="c0"/>
|
||||
<ColumnDefinition SharedSizeGroup="c1"/>
|
||||
<ColumnDefinition SharedSizeGroup="c2"/>
|
||||
<ColumnDefinition SharedSizeGroup="c3"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<Border Grid.Column="1" Grid.ColumnSpan="5" Background="White" BorderBrush="{StaticResource LadderColumnDetailsBorderBrush}" BorderThickness="1" Margin="0,-1" IsHitTestVisible="False"/>
|
||||
<TextBlock Margin="70,0,0,0" HorizontalAlignment="Left" VerticalAlignment="Center" Foreground="{StaticResource LadderColumnDetailsForegroundBrush}" Text="{Binding name}"/>
|
||||
<CheckBox Grid.Column="1" HorizontalAlignment="Center" VerticalAlignment="Center" IsChecked="{Binding bars.enabled}"/>
|
||||
<c:ColorPickerPopup
|
||||
Grid.Column="2"
|
||||
BorderBrush="Transparent"
|
||||
BorderThickness="0"
|
||||
PopupPlacement="Left"
|
||||
Palettes="{Binding palettes}"
|
||||
SelectedColor="{Binding bars.value}"
|
||||
/>
|
||||
<CheckBox Grid.Column="3" HorizontalAlignment="Center" VerticalAlignment="Center" IsChecked="{Binding show_numbers}"/>
|
||||
</Grid>
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
</ItemsControl>
|
||||
</Grid>
|
||||
</Border>
|
||||
</StackPanel>
|
||||
</DataTemplate>
|
||||
</u:TemplateSelectorKeyValue>
|
||||
</u:KeyedTemplateSelector>
|
||||
</ResourceDictionary>
|
||||
</UserControl.Resources>
|
||||
<TabControl>
|
||||
<TabControl.ItemContainerStyle>
|
||||
<Style TargetType="TabItem" BasedOn="{StaticResource {x:Type TabItem}}">
|
||||
<Setter Property="VerticalAlignment" Value="Stretch"/>
|
||||
<Setter Property="VerticalContentAlignment" Value="Top"/>
|
||||
</Style>
|
||||
</TabControl.ItemContainerStyle>
|
||||
<TabControl BorderThickness="1,1,1,0" ItemContainerStyle="{StaticResource FlatTabItem}" Style="{StaticResource FlatTabControl}">
|
||||
<!-- Ladder Columns Tab -->
|
||||
<TabItem Header="Ladder Columns" VerticalAlignment="Stretch">
|
||||
<Grid Grid.IsSharedSizeScope="True" Margin="8,0">
|
||||
<TabItem Header="Ladder Columns">
|
||||
<Grid Grid.IsSharedSizeScope="True">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="27"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
@@ -504,7 +503,7 @@
|
||||
</Grid>
|
||||
</TabItem>
|
||||
<!-- Market Break Lines Tab -->
|
||||
<TabItem Header="Market Break Lines" VerticalAlignment="Stretch">
|
||||
<TabItem Header="Price Lines">
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="27"/>
|
||||
@@ -623,14 +622,14 @@
|
||||
</Grid>
|
||||
</TabItem>
|
||||
<!-- Market Price & Size Tab -->
|
||||
<TabItem Header="Market Price and Size Display">
|
||||
<TabItem Header="Price & Size Display">
|
||||
<Grid Margin="6" Grid.IsSharedSizeScope="True">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="{Binding Source={StaticResource HorizontalPush3}}"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<GroupBox Header="Size Display" DataContext="{Binding size_display}" Padding="4">
|
||||
<GroupBox Header="Size Display" DataContext="{Binding size_display}" Padding="4">
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
@@ -644,7 +643,7 @@
|
||||
ItemsSource="{Binding format_options}"
|
||||
SelectedItem="{Binding selected_format}"
|
||||
/>
|
||||
<StackPanel Grid.Row="1" Grid.ColumnSpan="2" Orientation="Horizontal">
|
||||
<StackPanel Grid.Row="1" Grid.ColumnSpan="2" Orientation="Horizontal" VerticalAlignment="Top">
|
||||
<TextBlock
|
||||
Margin="{StaticResource MarginIndentChild}"
|
||||
Text="when implied is > 0"
|
||||
@@ -720,8 +719,8 @@
|
||||
</Grid>
|
||||
</TabItem>
|
||||
<!-- Size Presets Tab-->
|
||||
<TabItem Header="Order Quantities" VerticalAlignment="Stretch">
|
||||
<Grid HorizontalAlignment="Left" Margin="6">
|
||||
<TabItem Header="Quantity Buttons">
|
||||
<Grid HorizontalAlignment="Left" Margin="6" VerticalAlignment="Top">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="{Binding Source={StaticResource VerticalPush}}" MaxHeight="{Binding Source={StaticResource VerticalPush}}"/>
|
||||
@@ -777,7 +776,7 @@
|
||||
</Grid>
|
||||
</TabItem>
|
||||
<!-- Algo Favorites-->
|
||||
<TabItem Header="Algo Favorites" VerticalAlignment="Stretch">
|
||||
<TabItem Header="Algo Favorites">
|
||||
<Grid HorizontalAlignment="Left" Margin="{StaticResource Margin.UP1}" DataContext="{Binding favorite_algos_vm}">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/>
|
||||
@@ -815,7 +814,7 @@
|
||||
</DataTemplate>
|
||||
</ComboBox.ItemTemplate>
|
||||
</ComboBox>
|
||||
<CheckBox Grid.Column="2" VerticalAlignment="Center" VerticalContentAlignment="Center" IsChecked="{Binding is_sticky}"> Stay Enabled?</CheckBox>
|
||||
<CheckBox Grid.Column="2" VerticalAlignment="Center" VerticalContentAlignment="Center" IsChecked="{Binding is_sticky}">Stay Enabled?</CheckBox>
|
||||
</Grid>
|
||||
</DataTemplate>
|
||||
</u:KeyedTemplateSelector.Default>
|
||||
@@ -837,7 +836,7 @@
|
||||
</Grid>
|
||||
</TabItem>
|
||||
<!-- Order Entry Panel Item Control Visbility -->
|
||||
<TabItem Header="Left Panel" VerticalAlignment="Stretch">
|
||||
<TabItem Header="Left Panel">
|
||||
<Grid Margin="6">
|
||||
<Grid.Resources>
|
||||
<sys:Double x:Key="ValueRowHeight">22</sys:Double>
|
||||
@@ -911,7 +910,8 @@
|
||||
<!--Account-->
|
||||
<TextBlock Grid.Row="4" Grid.Column="0" VerticalAlignment="Center">Account</TextBlock>
|
||||
<ComboBox Grid.Row="4" Grid.Column="2" ItemsSource="{Binding accounts_view}" SelectedItem="{Binding default_account, Delay=1}"/>
|
||||
<!--Time In Force-->
|
||||
|
||||
<!--Time In Force-->
|
||||
<TextBlock Grid.Row="6" Grid.Column="0" VerticalAlignment="Center">Time In Force</TextBlock>
|
||||
<ComboBox Grid.Row="6" Grid.Column="2" ItemsSource="{Binding time_in_force_options}" SelectedItem="{Binding default_time_in_force, Delay=1}"/>
|
||||
<!--Algo-->
|
||||
@@ -964,30 +964,9 @@
|
||||
</ItemsControl.ItemTemplate>
|
||||
</ItemsControl>
|
||||
</GroupBox>
|
||||
<!--
|
||||
<ItemsControl ItemsSource="{Binding order_entry_panel_items}">
|
||||
<ItemsControl.ItemsPanel>
|
||||
<ItemsPanelTemplate>
|
||||
<UniformGrid Columns="2" Focusable="False"/>
|
||||
</ItemsPanelTemplate>
|
||||
</ItemsControl.ItemsPanel>
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="52"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBlock Grid.Column="0" VerticalAlignment="Center" Text="{Binding name}"/>
|
||||
<CheckBox Grid.Column="1" HorizontalAlignment="Center" VerticalAlignment="Center" IsChecked="{Binding show}"/>
|
||||
</Grid>
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
</ItemsControl>
|
||||
-->
|
||||
</Grid>
|
||||
</TabItem>
|
||||
<TabItem Header="Safety Checks" VerticalAlignment="Stretch">
|
||||
<TabItem Header="Safety Checks">
|
||||
<Grid HorizontalAlignment="Left" Margin="6">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/>
|
||||
@@ -1008,8 +987,28 @@
|
||||
</CheckBox>
|
||||
</Grid>
|
||||
</TabItem>
|
||||
<TabItem Header="Mini Order Book">
|
||||
<usp:GridSettingsView Height="790" Margin="6" DataContext="{Binding mini_ob}" GridSectionContent="{Binding}" GridSectionContentTemplate="{DynamicResource GridSectionCoreContentTemplate}"/>
|
||||
<TabItem Header="Order Popup">
|
||||
<TabControl BorderThickness="0,1,1,1" Margin="0,8,8,8" TabStripPlacement="Right" Style="{StaticResource FlatTabControl}">
|
||||
<TabControl.ItemContainerStyle>
|
||||
<Style TargetType="TabItem" BasedOn="{StaticResource FlatTabItem}">
|
||||
<Setter Property="HorizontalAlignment" Value="Left"/>
|
||||
<Style.Triggers>
|
||||
<Trigger Property="IsSelected" Value="True">
|
||||
<Setter Property="MinWidth" Value="66"/>
|
||||
</Trigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
</TabControl.ItemContainerStyle>
|
||||
<TabItem Header="Grid">
|
||||
<ItemsControl Grid.IsSharedSizeScope="True" ItemsSource="{Binding mini_ob.parameters}"/>
|
||||
</TabItem>
|
||||
<TabItem
|
||||
Header="Columns"
|
||||
Content="{Binding mini_ob}"
|
||||
ContentTemplate="{StaticResource ColumnsTabContent}"
|
||||
Margin="0,-1,0,0"
|
||||
/>
|
||||
</TabControl>
|
||||
</TabItem>
|
||||
</TabControl>
|
||||
</UserControl>
|
||||
</UserControl>
|
||||
@@ -29,6 +29,7 @@ using static mbts.ui.controls.DIYGrid;
|
||||
using OrderType = bts.api.orderman.CustomOrderDefinition.CustomOneofCase;
|
||||
using System.Windows.Input;
|
||||
using System.Reactive.Disposables;
|
||||
using mbts.ui.dpi;
|
||||
|
||||
namespace mbts.spark {
|
||||
// This interface is used to hide ColumnBase.SortDirection, and offer a SortDirection member that does NOT require a DependencyProperty lookup.
|
||||
@@ -46,10 +47,10 @@ namespace mbts.spark {
|
||||
public OrderBookSortingData(IEnumerable<ICVOrderBookColumn> column_sorting_hierarchy) {
|
||||
var comp_builder = new ComparerBuilder<OrderBookRowVM>();
|
||||
foreach (var col in column_sorting_hierarchy)
|
||||
comp_builder.add(col.SortDirection.Value == ListSortDirection.Ascending ? col.comparer : col.comparer.reverse());
|
||||
comp_builder.Add(col.SortDirection.Value == ListSortDirection.Ascending ? col.comparer : col.comparer.reverse());
|
||||
if (!column_sorting_hierarchy.Any(col => col.key == Columns.OrderKey)) {
|
||||
// the resultant comparer must to be 100% deterministic, as SortedList (the building block of the SortedTree) uses a BinarySearch to find/remove items and does NOT check for item-equality.
|
||||
comp_builder.add(Comparer<OrderBookRowVM>.Create((l, r) => l.order_key.CompareTo(r.order_key)));
|
||||
comp_builder.Add(Comparer<OrderBookRowVM>.Create((l, r) => l.order_key.CompareTo(r.order_key)));
|
||||
}
|
||||
comparer = comp_builder.to_comparer();
|
||||
sorting_criterion = column_sorting_hierarchy.Select(col => new SortingCriteria(col.id, col.SortDirection.GetValueOrDefault() == ListSortDirection.Ascending)).ToArray();
|
||||
@@ -582,7 +583,7 @@ namespace mbts.spark {
|
||||
|
||||
public void draw(OrderBookRowVM row, DrawingContext dc, ColumnGeometry position, double row_height) =>
|
||||
CellRender.draw_text(dc, _get_glyph_context(row), font_size, _get_text(row), position, row_height,
|
||||
RenderUtils.get_foreground(row, font_brush), null, horizontal_alignment, VerticalAlignment.Center, DefaultCellPadding);
|
||||
RenderUtils.get_foreground(row, font_brush), background_brush, horizontal_alignment, VerticalAlignment.Center, DefaultCellPadding);
|
||||
}
|
||||
|
||||
#region Explicit Column Defs
|
||||
@@ -592,7 +593,7 @@ namespace mbts.spark {
|
||||
|
||||
public void draw(OrderBookRowVM row, DrawingContext dc, ColumnGeometry position, double row_height) =>
|
||||
CellRender.draw_text(dc, _get_glyph_context(row), font_size, row.side.ToString(), position, row_height,
|
||||
RenderUtils.get_side_brush(row), null, horizontal_alignment, VerticalAlignment.Center, DefaultCellPadding);
|
||||
RenderUtils.get_side_brush(row), background_brush, horizontal_alignment, VerticalAlignment.Center, DefaultCellPadding);
|
||||
}
|
||||
|
||||
public class SizeColumn : OrderbookColumnBase<UIntMinMaxState>, IRenderColumn<OrderBookRowVM> {
|
||||
@@ -601,7 +602,7 @@ namespace mbts.spark {
|
||||
|
||||
public void draw(OrderBookRowVM row, DrawingContext dc, ColumnGeometry position, double row_height) =>
|
||||
CellRender.draw_text(dc, _get_glyph_context(row), font_size, row.quantity.ToString(), position, row_height,
|
||||
RenderUtils.get_side_brush(row), null, horizontal_alignment, VerticalAlignment.Center, DefaultCellPadding);
|
||||
RenderUtils.get_side_brush(row), background_brush, horizontal_alignment, VerticalAlignment.Center, DefaultCellPadding);
|
||||
}
|
||||
|
||||
public class FilledColumn : OrderbookColumnBase<UIntMinMaxState>, IRenderColumn<OrderBookRowVM> {
|
||||
@@ -610,7 +611,7 @@ namespace mbts.spark {
|
||||
|
||||
public void draw(OrderBookRowVM row, DrawingContext dc, ColumnGeometry position, double row_height) =>
|
||||
CellRender.draw_text(dc, _get_glyph_context(row), font_size, row.filled_quantity.ToString(), position, row_height,
|
||||
RenderUtils.get_side_brush(row), null, horizontal_alignment, VerticalAlignment.Center, DefaultCellPadding);
|
||||
RenderUtils.get_side_brush(row), background_brush, horizontal_alignment, VerticalAlignment.Center, DefaultCellPadding);
|
||||
}
|
||||
|
||||
public class OrderBookOrderTypeColumn : OrderTypeColumn<Columns>, ICVOrderBookColumn {
|
||||
@@ -676,7 +677,7 @@ namespace mbts.spark {
|
||||
|
||||
public void draw(OrderBookRowVM row, DrawingContext dc, ColumnGeometry position, double row_height) =>
|
||||
CellRender.draw_text(dc, _get_glyph_context(row), font_size, row.piq_text, position, row_height,
|
||||
RenderUtils.get_foreground(row, font_brush), null, HorizontalAlignment.Right, VerticalAlignment.Center, DefaultCellPadding);
|
||||
RenderUtils.get_foreground(row, font_brush), background_brush, HorizontalAlignment.Right, VerticalAlignment.Center, DefaultCellPadding);
|
||||
}
|
||||
|
||||
public class SecurityColumn : FrameworkElementColumnBase<Columns>, ICVOrderBookColumn, IFilterableColumn {
|
||||
@@ -720,14 +721,23 @@ namespace mbts.spark {
|
||||
security_view.SetBinding(TextBlock.FontFamilyProperty, new Binding(nameof(font_family)) { Source = this });
|
||||
security_view.SetBinding(TextBlock.FontWeightProperty, _font_weight_binding);
|
||||
//
|
||||
var grid = new Grid { VerticalAlignment = VerticalAlignment.Center };
|
||||
Grid.SetColumn(security_view, 1);
|
||||
var grid = new Grid {
|
||||
VerticalAlignment = VerticalAlignment.Center
|
||||
};
|
||||
Grid.SetColumn(toggle_button, 1);
|
||||
Grid.SetColumn(security_view, 2);
|
||||
grid.ColumnDefinitions.Add(new ColumnDefinition { Width = new GridLength(10.0.RoundLayoutValue(), GridUnitType.Pixel) });
|
||||
grid.ColumnDefinitions.Add(new ColumnDefinition { Width = GridLength.Auto });
|
||||
grid.ColumnDefinitions.Add(new ColumnDefinition { Width = new GridLength(1, GridUnitType.Star) });
|
||||
grid.Children.Add(toggle_button);
|
||||
grid.Children.Add(security_view);
|
||||
//
|
||||
return new Border { Child = grid, Margin = new Thickness(10, 0, 0, 0) };
|
||||
var container = new Grid {
|
||||
HorizontalAlignment = HorizontalAlignment.Stretch,
|
||||
VerticalAlignment = VerticalAlignment.Stretch
|
||||
};
|
||||
container.SetBinding(Control.BackgroundProperty, new Binding(nameof(background_brush)) { Source = this });
|
||||
container.Children.Add(grid);
|
||||
return container;
|
||||
}
|
||||
|
||||
protected override void OnSortDirectionChanged(ListSortDirection? new_value) {
|
||||
|
||||
@@ -141,22 +141,24 @@ namespace mbts.spark {
|
||||
toggle_show_fills_cmd = new DelegateCommand(() => show_fills = !show_fills);
|
||||
launch_fills_settings_cmd = concated_disposable(new DialogCommand(() => {
|
||||
const string title = "Order Book 'Mini' Fills Grid";
|
||||
var vm = new ShellSettingsDialogVM<GridSettings, SimpleGridSettingsVM>(
|
||||
var vm = new ShellSettingsDialogVM<GridSettings, GridSettingsVM>(
|
||||
_fills_settings_manager,
|
||||
us => us.fills_grid,
|
||||
UserSettings.FileExtensions.GRID,
|
||||
s => fills_vm.value.grid_settings_manager.create_vm(
|
||||
s => {
|
||||
var gs_vm = fills_vm.value.grid_settings_manager.create_vm();
|
||||
// we need to get the most up to date GridSettings when launching b/c users may
|
||||
// have changed some settings on the grid directly, like column widths.
|
||||
s.with(fills_vm.columns.Select(c => c.create()))
|
||||
),
|
||||
gs_vm.load(s.with(fills_vm.columns.Select(c => c.create())));
|
||||
return gs_vm;
|
||||
},
|
||||
show_save_as_defaults: false);
|
||||
// TODO: bind to main window's Title
|
||||
var dialog = new ShellSettingsDialog { Title = title, DataContext = vm };
|
||||
vm.RequestClose += (s, e) => WindowUtilities.force_close(dialog);
|
||||
return dialog;
|
||||
}));
|
||||
grid_settings_manager = new OrderBookGridSettingsManager(bus, notification_service, model, rows, core_columns);
|
||||
grid_settings_manager = new OrderBookGridSettingsManager(bus, notification_service, rows, core_columns);
|
||||
grid_settings_manager.algo_tree_display_mode_changed += _on_filter_changed;
|
||||
IDisposable attach_vm_hooks = null;
|
||||
begin_attaching += on_begin_attaching;
|
||||
@@ -245,7 +247,7 @@ namespace mbts.spark {
|
||||
.Subscribe(is_visible => {
|
||||
if (is_visible) {
|
||||
var init = _cached_fills_settings.to_startup_data();
|
||||
fills_vm.value = new FillsVM(bus, model, Observable.Return(init), model.on_fill_tuples.Filter(mk_fills_slave_predicate_src(), ft => ft.fill.FillKey, _ => false), _fills_settings_manager);
|
||||
fills_vm.value = new FillsVM(bus, model, init, model.on_fill_tuples.Filter(mk_fills_slave_predicate_src(), ft => ft.fill.FillKey, _ => false), _fills_settings_manager);
|
||||
} else if (fills_vm.value != null) {
|
||||
_cached_fills_settings = _get_fills_settings(fills_vm.value);
|
||||
fills_vm.value = null;
|
||||
@@ -261,7 +263,8 @@ namespace mbts.spark {
|
||||
}),
|
||||
_columns);
|
||||
if (settings_manager != null) {
|
||||
concat_disposable(settings_manager.subscribe_current().OnDispatcher().Subscribe(grid_settings_manager));
|
||||
concat_disposable(
|
||||
settings_manager.subscribe_current().OnDispatcher().Subscribe(grid_settings_manager.load_from));
|
||||
}
|
||||
IObservable<Func<FillTuple, bool>> mk_fills_slave_predicate_src() =>
|
||||
Observable.Create<Func<FillTuple, bool>>(o => {
|
||||
|
||||
@@ -176,8 +176,10 @@ namespace mbts.spark {
|
||||
return _vm.grid_settings_manager.create_vm(s);
|
||||
}
|
||||
);
|
||||
gs_vm.SubscribeDisposed(literal_title_source.OnDispatcher().Subscribe(t => gs_vm.title = t));
|
||||
var dialog = new ShellSettingsDialog { DataContext = gs_vm };
|
||||
gs_vm.SubscribeDisposed(Disposable.Create(
|
||||
literal_title_source.OnDispatcher().Subscribe(t => gs_vm.title = t),
|
||||
_bus.subscribe(SparkTags.SecuritiesTag).OnDispatcher().Subscribe(sec_map => dialog.Resources[ResourceKeys.Securities] = sec_map)));
|
||||
gs_vm.RequestClose += (s, e) => WindowUtilities.force_close(dialog);
|
||||
return dialog;
|
||||
}));
|
||||
|
||||
@@ -1,15 +1,17 @@
|
||||
using bts.api;
|
||||
using bts.settings;
|
||||
using bts.shell.orderbook;
|
||||
using bts.spark;
|
||||
using mbts.persist;
|
||||
using mbts.spark;
|
||||
using mbts.ui;
|
||||
using System;
|
||||
using System.ComponentModel.Composition;
|
||||
using System.Reactive.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace bts.shell {
|
||||
internal class OrderBookWindowFactory : ShellWindowFactory<OrderBookStartupData> {
|
||||
class OrderBookWindowFactory : ShellWindowFactory<OrderBookStartupData> {
|
||||
readonly IBus _bus;
|
||||
readonly INotificationService _notification_service;
|
||||
readonly INotificationServiceDataCache _notification_service_data_cache;
|
||||
@@ -26,9 +28,7 @@ namespace bts.shell {
|
||||
}
|
||||
|
||||
public override Task<IShellWindow> load(OrderBookStartupData state) {
|
||||
var settings_manager = new WindowSettingsManager<OrderBookGridSettings>(_settings_hub, state.grid_settings_wrapper(),
|
||||
us => us.order_book_grid ?? OrderBookStartupData.DefaultGrid,
|
||||
(us, s) => us.with(order_book_grid: s));
|
||||
var settings_manager = _settings_hub.create_orderbook_settings_manager(state.grid_settings_wrapper());
|
||||
var w = new OrderBookWindow(_model, this, _bus, _notification_service, state, SparkCommands.ShowOrders.icon, settings_manager.as_non_shared());
|
||||
return Task.FromResult<IShellWindow>(w);
|
||||
}
|
||||
@@ -37,5 +37,17 @@ namespace bts.shell {
|
||||
var w = await load(startup_data);
|
||||
_bus.post(SparkTags.ShowWindow, w);
|
||||
}
|
||||
|
||||
[Export(typeof(IOrderBookGridSettingsMigrationRule))]
|
||||
class EnableBackgroundMigrationRule : IOrderBookGridSettingsMigrationRule {
|
||||
public bool apply(OrderBookGridSettings settings, out OrderBookGridSettings new_settings) {
|
||||
if (!settings.general.enable_background_color) {
|
||||
new_settings = settings.with(general: settings.general.with(enable_background_color: true));
|
||||
return true;
|
||||
}
|
||||
new_settings = default;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -7,6 +7,7 @@ using ProtoBuf;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Linq;
|
||||
using GridSettings = mbts.ui.settings.GridSettings;
|
||||
|
||||
namespace bts.shell.orderbook {
|
||||
@@ -42,8 +43,8 @@ namespace bts.shell.orderbook {
|
||||
public IReadOnlyList<OrderRecordTuplePredicateAnimations> predicate_animations => _predicate_animations ?? (_predicate_animations = new List<OrderRecordTuplePredicateAnimations>());
|
||||
IEnumerable<IPredicateAnimationsData<OrderSecurity, OrderRecordTuplePredicate>> IGridSettingsWithNotifications<OrderSecurity, OrderRecordTuplePredicate>.predicate_animations => predicate_animations;
|
||||
|
||||
public OrderBookGridSettings with(GridSettings general) =>
|
||||
new OrderBookGridSettings(general ?? this.general, predicate_animations) {
|
||||
public OrderBookGridSettings with(GridSettings general = null, IEnumerable<OrderRecordTuplePredicateAnimations> predicate_animations = null) =>
|
||||
new OrderBookGridSettings(general ?? this.general, predicate_animations?.ToList() ?? this.predicate_animations) {
|
||||
algo_tree_display_mode = algo_tree_display_mode
|
||||
};
|
||||
|
||||
|
||||
+37
-48
@@ -1,23 +1,27 @@
|
||||
using bts.api;
|
||||
using bts.api.orderman;
|
||||
using mbts;
|
||||
using mbts.spark;
|
||||
using mbts.ui;
|
||||
using mbts.ui.controls;
|
||||
using mbts.ui.settings;
|
||||
using mbts.ui.utility;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
using Columns = mbts.spark.OrderBookGridView.ColumnKeys;
|
||||
using CompositeDisposable = System.Reactive.Disposables.CompositeDisposable;
|
||||
|
||||
namespace bts.shell.orderbook {
|
||||
class OrderBookGridSettingsManager : IDisposable, IObserver<OrderBookGridSettings> {
|
||||
class OrderBookGridSettingsManager : IDisposable {
|
||||
public static readonly IReadOnlyDictionary<string, GridColumnSettingsFeatureFlags> hard_column_flags;
|
||||
|
||||
readonly IObserver<OrderBookGridSettings> _core_man_as_observer;
|
||||
readonly IEnumerable<OrderBookRowVM> _rows;
|
||||
readonly ObservableCollection<OrderRecordTuplePredicateAnimations> _active_predicate_animations =
|
||||
new ObservableCollection<OrderRecordTuplePredicateAnimations>();
|
||||
|
||||
IReadOnlyList<OrderRecordTuplePredicateAnimations> _predicate_animations;
|
||||
AlgoTreeDisplayMode _algo_tree_display_mode;
|
||||
IDisposable _sound_hooks;
|
||||
|
||||
static OrderBookGridSettingsManager() {
|
||||
// make font-weight immutable for all columns (the font-weight rule is hard-coded: Regular for root-orders, Light for all others)
|
||||
@@ -27,7 +31,7 @@ namespace bts.shell.orderbook {
|
||||
// make font-color immutable for Side, Size, and Filled columns (the font-color rule is hard-coded: MarketData.bid for Buy orders, MarketData.ask for Sells)
|
||||
// TODO: Remove the immutable font-color flags when cell animation is ready
|
||||
foreach (var c in new[] { Columns.Side, Columns.Size, Columns.Filled }) {
|
||||
col_settings_flags[c.ToString()] |= GridColumnSettingsFeatureFlags.ImmutableFontWeight;
|
||||
col_settings_flags[c.ToString()] |= GridColumnSettingsFeatureFlags.ImmutableFontColor;
|
||||
}
|
||||
col_settings_flags[Columns.Commit.ToString()] |= GridColumnSettingsFeatureFlags.ImmutableHeader;
|
||||
hard_column_flags = col_settings_flags;
|
||||
@@ -36,36 +40,22 @@ namespace bts.shell.orderbook {
|
||||
public OrderBookGridSettingsManager(
|
||||
IBus bus,
|
||||
INotificationService notification_service,
|
||||
OrdersModel model,
|
||||
IEnumerable<OrderBookRowVM> rows,
|
||||
ObservableCollection<DIYGrid.ColumnBase> columns) {
|
||||
core_manager = new GridSettingsManager<OrderBookRowVM, OrderSecurity, OrderRecordTuplePredicate, OrderBookGridSettings>(
|
||||
bus, model, rows, columns,
|
||||
() => new OrderRecordTuplePredicate(),
|
||||
() => {
|
||||
var pvm = PredicateVMWrapper.create<OrderSecurity, OrderRecordTuplePredicate, OrderRecordTuplePredicate.PredicateTypeOneofCase>(
|
||||
OrderRecordTuplePredicateVMFactory.get_oneofs(bus, model), rp => rp.predicate_type_case);
|
||||
pvm.selected_predicate_type = pvm.predicate_types.FirstOrDefault();
|
||||
return pvm;
|
||||
},
|
||||
col_id => {
|
||||
var pvm = PredicateVMWrapper.create<OrderSecurity, OrderRecordTuplePredicate, OrderRecordTuplePredicate.PredicateTypeOneofCase>(
|
||||
OrderRecordTuplePredicateVMFactory.get_oneofs(bus, model), rp => rp.predicate_type_case);
|
||||
pvm.selected_predicate_type = _corresponding_predicate_type(EnumHelper.parse_or_default<Columns>(col_id));
|
||||
return pvm;
|
||||
},
|
||||
(gen, anims) => new OrderBookGridSettings(gen, anims.empty_if_null().Select(a => new OrderRecordTuplePredicateAnimations(a.predicate_data, a.cell_animations, a.row_animations, a.sound_notifications))) { algo_tree_display_mode = _algo_tree_display_mode },
|
||||
p => p.create_predicate(notification_service.data_cache),
|
||||
p => p.create_notification_predicate(notification_service.data_cache),
|
||||
notification_service == null ? new Func<IPredicate<OrderSecurity>, SoundNotification, IDisposable>((p, sn) => Disposable.empty) : notification_service.link_to_order,
|
||||
notification_service == null ? new Func<INotificationPredicate<OrderSecurity>, IDisposable>(t => Disposable.empty) : notification_service.link_to_order,
|
||||
hard_column_flags);
|
||||
_core_man_as_observer = core_manager;
|
||||
_rows = rows;
|
||||
core_manager = new GridSettingsManager(columns, hard_column_flags);
|
||||
_sound_hooks = ObservableAutoWrapper.connect_item_hooks(
|
||||
_active_predicate_animations,
|
||||
a => {
|
||||
var pred = a.predicate_data.create_predicate(notification_service.data_cache);
|
||||
return new CompositeDisposable(
|
||||
a.sound_notifications.Select(s => notification_service.link_to_order(pred, s)));
|
||||
});
|
||||
}
|
||||
|
||||
public event Action algo_tree_display_mode_changed;
|
||||
|
||||
public GridSettingsManager<OrderBookRowVM, OrderSecurity, OrderRecordTuplePredicate, OrderBookGridSettings> core_manager { get; }
|
||||
public GridSettingsManager core_manager { get; }
|
||||
|
||||
public AlgoTreeDisplayMode algo_tree_display_mode {
|
||||
get => _algo_tree_display_mode;
|
||||
@@ -78,23 +68,29 @@ namespace bts.shell.orderbook {
|
||||
}
|
||||
|
||||
public void load_from(OrderBookGridSettings gs) {
|
||||
core_manager.load_from(gs);
|
||||
core_manager.load_from(gs.general);
|
||||
algo_tree_display_mode = gs.algo_tree_display_mode;
|
||||
_predicate_animations = gs.predicate_animations;
|
||||
_active_predicate_animations.sync_with(_predicate_animations.Where(p => p.is_enabled));
|
||||
foreach (var r in _rows)
|
||||
r.raise_invalidated();
|
||||
}
|
||||
|
||||
public OrderBookGridSettings create(IEnumerable<SortingCriteria> sorting_criterion) {
|
||||
var gs = core_manager.create();
|
||||
gs.general = gs.general.with(sorting_criterion: sorting_criterion);
|
||||
gs.algo_tree_display_mode = _algo_tree_display_mode;
|
||||
return gs;
|
||||
}
|
||||
|
||||
public OrderBookSettingsVM create_vm(OrderBookGridSettings gs) =>
|
||||
new OrderBookSettingsVM(core_manager.create_vm(gs.general)) {
|
||||
algo_tree_display_mode = gs.algo_tree_display_mode
|
||||
public OrderBookGridSettings create(IEnumerable<SortingCriteria> sorting_criterion) =>
|
||||
new OrderBookGridSettings(
|
||||
core_manager.create(),
|
||||
_predicate_animations.ToList()) {
|
||||
algo_tree_display_mode = _algo_tree_display_mode
|
||||
};
|
||||
|
||||
public void Dispose() => core_manager.Dispose();
|
||||
public OrderBookSettingsVM create_vm(OrderBookGridSettings gs) {
|
||||
var vm = new OrderBookSettingsVM(core_manager.create_vm());
|
||||
vm.load(gs);
|
||||
return vm;
|
||||
}
|
||||
|
||||
public void Dispose() =>
|
||||
Disposable.dispose(ref _sound_hooks);
|
||||
|
||||
static OrderRecordTuplePredicate.PredicateTypeOneofCase _corresponding_predicate_type(Columns col) {
|
||||
switch (col) {
|
||||
@@ -122,12 +118,5 @@ namespace bts.shell.orderbook {
|
||||
default: return default;
|
||||
}
|
||||
}
|
||||
|
||||
void IObserver<OrderBookGridSettings>.OnNext(OrderBookGridSettings value) {
|
||||
_core_man_as_observer.OnNext(value);
|
||||
algo_tree_display_mode = value.algo_tree_display_mode;
|
||||
}
|
||||
void IObserver<OrderBookGridSettings>.OnError(Exception error) => _core_man_as_observer.OnError(error);
|
||||
void IObserver<OrderBookGridSettings>.OnCompleted() => _core_man_as_observer.OnCompleted();
|
||||
}
|
||||
}
|
||||
@@ -1,46 +1,143 @@
|
||||
using bts.api;
|
||||
using bts.api.orderman;
|
||||
using mbts;
|
||||
using mbts.spark;
|
||||
using mbts.ui;
|
||||
using mbts.ui.settings;
|
||||
using mbts.ui.shell;
|
||||
using mbts.ui.spark;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace bts.shell.orderbook {
|
||||
class OrderBookSettingsVM : DisposableViewModelBase, IWindowSettingsVM<OrderBookGridSettings>, ITemplateSelectorKey {
|
||||
AlgoTreeDisplayMode _algo_tree_display_mode;
|
||||
|
||||
public OrderBookSettingsVM(GridSettingsVM<OrderSecurity, OrderRecordTuplePredicate, OrderBookGridSettings> core_settings_vm) =>
|
||||
this.core_settings_vm = core_settings_vm;
|
||||
|
||||
public OrderBookSettingsVM(
|
||||
IBus bus,
|
||||
OrdersModel orders_model,
|
||||
Func<OrderRecordTuplePredicate> predicate_data_factory,
|
||||
Func<OrderRecordTuplePredicate, INotificationPredicate<OrderSecurity>> notification_predicate_factory,
|
||||
Func<IPropertyWrapperVM<OrderRecordTuplePredicate>> create_predicate_vm,
|
||||
Func<string, IPropertyWrapperVM<OrderRecordTuplePredicate>> create_column_predicate_vm,
|
||||
Func<GridSettings, IEnumerable<IPredicateAnimationsData<OrderSecurity, OrderRecordTuplePredicate>>, OrderBookGridSettings> create_settings_obj,
|
||||
IReadOnlyDictionary<string, GridColumnSettingsFeatureFlags> column_flags = null) {
|
||||
core_settings_vm = new GridSettingsVM<OrderSecurity, OrderRecordTuplePredicate, OrderBookGridSettings>(
|
||||
bus, orders_model, predicate_data_factory, notification_predicate_factory, create_predicate_vm, create_column_predicate_vm, create_settings_obj, column_flags);
|
||||
}
|
||||
|
||||
public GridSettingsVM<OrderSecurity, OrderRecordTuplePredicate, OrderBookGridSettings> core_settings_vm { get; }
|
||||
public AlgoTreeDisplayMode algo_tree_display_mode { get => _algo_tree_display_mode; set => Set(ref _algo_tree_display_mode, value); }
|
||||
string ITemplateSelectorKey.key => GridSettingsTemplateKeys.OrderBook;
|
||||
|
||||
public void load(OrderBookGridSettings grid_settings) {
|
||||
core_settings_vm.load(grid_settings);
|
||||
algo_tree_display_mode = grid_settings.algo_tree_display_mode;
|
||||
}
|
||||
public OrderBookGridSettings get_result() {
|
||||
var gs = core_settings_vm.get_result();
|
||||
gs.algo_tree_display_mode = algo_tree_display_mode;
|
||||
return gs;
|
||||
}
|
||||
}
|
||||
using bts.api;
|
||||
using bts.utils;
|
||||
using mbts;
|
||||
using mbts.spark;
|
||||
using mbts.ui;
|
||||
using mbts.ui.shell;
|
||||
using mbts.ui.spark;
|
||||
using mbts.ui.viewmodels;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
using System.Windows.Input;
|
||||
|
||||
namespace bts.shell.orderbook {
|
||||
class OrderBookSettingsVM : DisposableViewModelBase, IWindowSettingsVM<OrderBookGridSettings>, ITemplateSelectorKey {
|
||||
readonly BooleanParameterVM _show_full_algo_tree = new BooleanParameterVM("Show Full Algo Tree");
|
||||
|
||||
public OrderBookSettingsVM(GridSettingsVM core_settings_vm) {
|
||||
this.core_settings_vm = core_settings_vm;
|
||||
this.echo(_show_full_algo_tree, nameof(algo_tree_display_mode));
|
||||
add_alert_cmd = new DelegateCommand(() => {
|
||||
alerts.Add(new OrderAlertVM {
|
||||
is_enabled = true,
|
||||
predicate_type = OrderRecordTuplePredicate.PredicateTypeOneofCase.FilledEvent,
|
||||
sound_type = SoundNotification.SystemSound.BellCounter
|
||||
});
|
||||
});
|
||||
delete_alert_cmd = new DelegateCommand<OrderAlertVM>(vm => {
|
||||
if (vm != null)
|
||||
alerts.Remove(vm);
|
||||
});
|
||||
}
|
||||
|
||||
public GridSettingsVM core_settings_vm { get; }
|
||||
public AlgoTreeDisplayMode algo_tree_display_mode {
|
||||
get => _show_full_algo_tree.value ? AlgoTreeDisplayMode.FullTree : AlgoTreeDisplayMode.RootAndLeafNodes;
|
||||
set => _show_full_algo_tree.value = value == AlgoTreeDisplayMode.FullTree;
|
||||
}
|
||||
public IEnumerable parameters {
|
||||
get {
|
||||
yield return _show_full_algo_tree;
|
||||
}
|
||||
}
|
||||
public ICommand add_alert_cmd { get; }
|
||||
public ICommand delete_alert_cmd { get; }
|
||||
// TODO: make this generic (for now, all views orderbook settings can ignore it)
|
||||
public ObservableCollection<OrderAlertVM> alerts { get; } = new ObservableCollection<OrderAlertVM>();
|
||||
string ITemplateSelectorKey.key => GridSettingsTemplateKeys.OrderBook;
|
||||
|
||||
public void load(OrderBookGridSettings grid_settings) {
|
||||
core_settings_vm.load(grid_settings.general);
|
||||
algo_tree_display_mode = grid_settings.algo_tree_display_mode;
|
||||
CollectionHelper.add_or_update_existing(alerts,
|
||||
grid_settings.predicate_animations,
|
||||
pa => {
|
||||
var vm = new OrderAlertVM();
|
||||
vm.load_from(pa);
|
||||
return vm;
|
||||
},
|
||||
(vm, pa) => vm.load_from(pa));
|
||||
}
|
||||
|
||||
public OrderBookGridSettings get_result() {
|
||||
var gs = core_settings_vm.get_result();
|
||||
return new OrderBookGridSettings(gs, alerts.Select(vm => vm.create())) {
|
||||
algo_tree_display_mode = algo_tree_display_mode
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
class OrderAlertVM : ViewModelBase, IValueFactory<OrderRecordTuplePredicateAnimations> {
|
||||
SecurityCriteria _security_criteria;
|
||||
SoundNotification.SystemSound _sound_type;
|
||||
OrderRecordTuplePredicate.PredicateTypeOneofCase _predicate_type;
|
||||
bool _is_enabled;
|
||||
|
||||
public bool is_enabled { get => _is_enabled; set => Set(ref _is_enabled, value); }
|
||||
public SecurityCriteria security_criteria { get => _security_criteria; set => Set(ref _security_criteria, value); }
|
||||
public SoundNotification.SystemSound sound_type { get => _sound_type; set => Set(ref _sound_type, value); }
|
||||
public OrderRecordTuplePredicate.PredicateTypeOneofCase predicate_type { get => _predicate_type; set => Set(ref _predicate_type, value); }
|
||||
public IEnumerable<OrderRecordTuplePredicate.PredicateTypeOneofCase> predicate_types { get; } =
|
||||
new[] {
|
||||
OrderRecordTuplePredicate.PredicateTypeOneofCase.FilledEvent,
|
||||
OrderRecordTuplePredicate.PredicateTypeOneofCase.CompletedEvent,
|
||||
OrderRecordTuplePredicate.PredicateTypeOneofCase.CanceledEvent
|
||||
};
|
||||
|
||||
public OrderRecordTuplePredicateAnimations create() {
|
||||
var pred = new OrderRecordTuplePredicate();
|
||||
switch (predicate_type) {
|
||||
case OrderRecordTuplePredicate.PredicateTypeOneofCase.CanceledEvent:
|
||||
pred.canceled_event = new OrderCanceledEventPredicate {
|
||||
security = security_criteria.is_defined() ?
|
||||
new SecurityPredicate { is_value = security_criteria } : null
|
||||
};
|
||||
break;
|
||||
case OrderRecordTuplePredicate.PredicateTypeOneofCase.FilledEvent:
|
||||
pred.filled_event = new OrderFilledEventPredicate {
|
||||
security = security_criteria.is_defined() ?
|
||||
new SecurityPredicate { is_value = security_criteria } : null
|
||||
};
|
||||
break;
|
||||
case OrderRecordTuplePredicate.PredicateTypeOneofCase.CompletedEvent:
|
||||
pred.completed_event = new OrderCompletedEventPredicate {
|
||||
security = security_criteria.is_defined() ?
|
||||
new SecurityPredicate { is_value = security_criteria } : null
|
||||
};
|
||||
break;
|
||||
}
|
||||
return new OrderRecordTuplePredicateAnimations(
|
||||
pred, null, null,
|
||||
new[] {
|
||||
new SoundNotification { system_sound = sound_type }
|
||||
},
|
||||
is_enabled);
|
||||
}
|
||||
|
||||
public void load_from(OrderRecordTuplePredicateAnimations value) {
|
||||
if (value.sound_notifications.Count == 0)
|
||||
return;
|
||||
is_enabled = value.is_enabled;
|
||||
var animation = value.sound_notifications[0];
|
||||
var pred = value.predicate_data;
|
||||
if (animation.sound_type_case == SoundNotification.SoundTypeOneofCase.System)
|
||||
sound_type = animation.system_sound.Value;
|
||||
if (pred != null) {
|
||||
predicate_type = pred.predicate_type_case;
|
||||
switch (pred.predicate_type_case) {
|
||||
case OrderRecordTuplePredicate.PredicateTypeOneofCase.CanceledEvent:
|
||||
security_criteria = pred.canceled_event.security?.is_value;
|
||||
break;
|
||||
case OrderRecordTuplePredicate.PredicateTypeOneofCase.CompletedEvent:
|
||||
security_criteria = pred.completed_event.security?.is_value;
|
||||
break;
|
||||
case OrderRecordTuplePredicate.PredicateTypeOneofCase.FilledEvent:
|
||||
security_criteria = pred.filled_event.security?.is_value;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,137 @@
|
||||
<UserControl
|
||||
x:Class="mbts.ui.spark.OrderBookSettingsView"
|
||||
x:ClassModifier="internal"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:shell="clr-namespace:bts.shell"
|
||||
xmlns:orderbook="clr-namespace:bts.shell.orderbook"
|
||||
xmlns:u="clr-namespace:mbts.ui"
|
||||
xmlns:c="clr-namespace:mbts.ui.controls"
|
||||
xmlns:usp="clr-namespace:mbts.ui.spark"
|
||||
xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
|
||||
Content="{Binding}"
|
||||
>
|
||||
<UserControl.Resources>
|
||||
<usp:GridSettingsResources/>
|
||||
</UserControl.Resources>
|
||||
<UserControl.ContentTemplate>
|
||||
<DataTemplate DataType="{x:Type orderbook:OrderBookSettingsVM}">
|
||||
<Grid>
|
||||
<u:BindingProxy x:Name="ActionMenuItemsViewProxy" Value="{DynamicResource ActionMenuItemsView}"/>
|
||||
<c:ActionMenu ItemsSource="{Binding Path='(u:BindingProxy.Value).(CollectionViewSource.View)', ElementName=ActionMenuItemsViewProxy}">
|
||||
<ItemsControl.GroupStyle>
|
||||
<GroupStyle ContainerStyle="{StaticResource ComboBox.DelineatedGroupContainerStyle.DynamicFill}"/>
|
||||
</ItemsControl.GroupStyle>
|
||||
</c:ActionMenu>
|
||||
<TabControl MinWidth="500" Margin="0,2,0,0" Style="{StaticResource FlatTabControl}">
|
||||
<TabItem Header="Grid">
|
||||
<ItemsControl x:Name="ParameterItemsControl" Grid.IsSharedSizeScope="True">
|
||||
<ItemsControl.ItemsSource>
|
||||
<CompositeCollection>
|
||||
<CollectionContainer Collection="{Binding DataContext.parameters, Source={u:DirectReference ParameterItemsControl}}"/>
|
||||
<CollectionContainer Collection="{Binding DataContext.core_settings_vm.parameters, Source={u:DirectReference ParameterItemsControl}}"/>
|
||||
</CompositeCollection>
|
||||
</ItemsControl.ItemsSource>
|
||||
</ItemsControl>
|
||||
</TabItem>
|
||||
<TabItem Header="Columns" Content="{Binding core_settings_vm}" ContentTemplate="{StaticResource ColumnsTabContent}"/>
|
||||
<TabItem Header="Alerts" DataContext="{Binding DataContext.instance_vm, RelativeSource={RelativeSource AncestorType=usp:ShellSettingsDialog}}">
|
||||
<Grid VerticalAlignment="Top">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="*"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
</Grid.RowDefinitions>
|
||||
<ScrollViewer HorizontalScrollBarVisibility="Hidden" VerticalScrollBarVisibility="Auto">
|
||||
<c:DIYGrid
|
||||
Rows="{Binding alerts}"
|
||||
BorderBrush="Black"
|
||||
BorderThickness="1"
|
||||
RowsHorizontalScrollBarVisibility="Collapsed"
|
||||
SelectionMode="Single"
|
||||
>
|
||||
<c:DIYGrid.Columns>
|
||||
<c:DIYGridColumnCollection>
|
||||
<c:DIYGridColumn key="Delete" HeaderContainerStyle="{StaticResource DisabledColumnHeader}" Width="23" is_visible="True"/>
|
||||
<c:DIYGridColumn key="IsEnabled" HeaderContainerStyle="{StaticResource DisabledColumnHeader}" Width="66" is_visible="True"/>
|
||||
<c:DIYGridColumn key="Security" Header="Security" Width="130" is_visible="True"/>
|
||||
<c:DIYGridColumn key="EventType" Header="Event Type" Width="120" is_visible="True"/>
|
||||
<c:DIYGridColumn key="Sound" Header="Sound" Width="130" is_visible="True"/>
|
||||
</c:DIYGridColumnCollection>
|
||||
</c:DIYGrid.Columns>
|
||||
<c:DIYGrid.RowTemplate>
|
||||
<DataTemplate>
|
||||
<Grid c:DIYGrid.SyncWithColumnGeometry="True">
|
||||
<Button
|
||||
c:DIYGrid.ColumnKey="Delete"
|
||||
HorizontalAlignment="Center"
|
||||
Command="{Binding DataContext.delete_alert_cmd, RelativeSource={RelativeSource AncestorType=c:DIYGrid}}"
|
||||
CommandParameter="{Binding}"
|
||||
Style="{StaticResource RoundCloseButton}"
|
||||
ToolTip="Delete"
|
||||
/>
|
||||
<ToggleButton
|
||||
c:DIYGrid.ColumnKey="IsEnabled"
|
||||
IsChecked="{Binding is_enabled}"
|
||||
Style="{StaticResource ToggleSwitch}"
|
||||
/>
|
||||
<ComboBox
|
||||
c:DIYGrid.ColumnKey="EventType"
|
||||
Margin="{StaticResource CellPadding}"
|
||||
ItemsSource="{Binding predicate_types}"
|
||||
ItemTemplate="{StaticResource EnumDescriptionTemplate}"
|
||||
SelectedItem="{Binding predicate_type}"
|
||||
/>
|
||||
<c:ContractPickerPopup
|
||||
c:DIYGrid.ColumnKey="Security"
|
||||
Margin="{StaticResource CellPadding}"
|
||||
AllowEmpty="True"
|
||||
CloseOnOK="True"
|
||||
CloseOnCancel="True"
|
||||
MaxWidth="180"
|
||||
DataContext="{Binding predicate}"
|
||||
SecurityMap="{DynamicResource {x:Static u:ResourceKeys.Securities}}"
|
||||
Header="{Binding security_criteria}"
|
||||
ShowDialogSkirt="True"
|
||||
ToolTip="{Binding security_criteria, Converter={x:Static c:ContractPickerPopup.SecurityCriteriaTextConverter}, Mode=OneWay}"
|
||||
OKClick="{x:Static c:ContractPickerPopup.FlushSelectedCriteriaHandler}"
|
||||
SelectedCriteria="{Binding security_criteria, UpdateSourceTrigger=Explicit}"
|
||||
>
|
||||
<c:ContractPickerPopup.HeaderTemplate>
|
||||
<DataTemplate>
|
||||
<TextBlock Text="{Binding Converter={x:Static c:ContractPickerPopup.SecurityCriteriaTextConverter}}" TextTrimming="CharacterEllipsis"/>
|
||||
</DataTemplate>
|
||||
</c:ContractPickerPopup.HeaderTemplate>
|
||||
</c:ContractPickerPopup>
|
||||
<ComboBox
|
||||
x:Name="sound_dropdown"
|
||||
c:DIYGrid.ColumnKey="Sound"
|
||||
Margin="{StaticResource CellPadding}"
|
||||
ItemsSource="{u:EnumBindingSource Type={u:Nested shell:SoundNotification.SystemSound}, Except=None}"
|
||||
ItemTemplate="{StaticResource EnumDescriptionTemplate}"
|
||||
SelectedItem="{Binding sound_type}"
|
||||
>
|
||||
<i:Interaction.Triggers>
|
||||
<i:EventTrigger EventName="DropDownClosed">
|
||||
<i:InvokeCommandAction Command="{x:Static c:SoundAnimationView.PreviewSoundCommand}" CommandParameter="{Binding SelectedItem, ElementName=sound_dropdown}"/>
|
||||
</i:EventTrigger>
|
||||
</i:Interaction.Triggers>
|
||||
</ComboBox>
|
||||
<Rectangle Grid.Column="0" HorizontalAlignment="Right" Width="1" Fill="Gray"/>
|
||||
<Rectangle Grid.Column="1" HorizontalAlignment="Right" Width="1" Fill="Gray"/>
|
||||
<Rectangle Grid.Column="2" HorizontalAlignment="Right" Width="1" Fill="Gray"/>
|
||||
<Rectangle Grid.Column="3" HorizontalAlignment="Right" Width="1" Fill="Gray"/>
|
||||
</Grid>
|
||||
</DataTemplate>
|
||||
</c:DIYGrid.RowTemplate>
|
||||
</c:DIYGrid>
|
||||
</ScrollViewer>
|
||||
<Button Grid.Row="1" HorizontalAlignment="Stretch" HorizontalContentAlignment="Center" VerticalAlignment="Top" VerticalContentAlignment="Center" Command="{Binding add_alert_cmd}">
|
||||
<TextBlock Margin="8,4" FontWeight="SemiBold">Add Alert</TextBlock>
|
||||
</Button>
|
||||
</Grid>
|
||||
</TabItem>
|
||||
</TabControl>
|
||||
</Grid>
|
||||
</DataTemplate>
|
||||
</UserControl.ContentTemplate>
|
||||
</UserControl>
|
||||
@@ -0,0 +1,35 @@
|
||||
using mbts.ui.converters;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Data;
|
||||
|
||||
namespace mbts.ui.spark {
|
||||
/// <summary>
|
||||
/// Interaction logic for OrderBookSettingsView.xaml
|
||||
/// </summary>
|
||||
partial class OrderBookSettingsView : UserControl {
|
||||
public static readonly IValueConverter
|
||||
DisplayNameVisualStateConverter = ValueConverter.create<bool, string>(can_override => can_override ? VisualStateNames.DisplayName.Active : VisualStateNames.DisplayName.Inactive),
|
||||
ColumnVisibilityToggleVisualStateConverter = ValueConverter.create<bool, string>(can_override => can_override ? VisualStateNames.ColumnVisibilityToggle.Active : VisualStateNames.ColumnVisibilityToggle.Inactive);
|
||||
|
||||
public OrderBookSettingsView() =>
|
||||
InitializeComponent();
|
||||
|
||||
public static class VisualStateNames {
|
||||
public static class DisplayName {
|
||||
public const string GroupName = nameof(DisplayName);
|
||||
|
||||
public static readonly string
|
||||
Active = $"{GroupName}{VisualStates.Active}",
|
||||
Inactive = $"{GroupName}{VisualStates.Inactive}";
|
||||
}
|
||||
|
||||
public static class ColumnVisibilityToggle {
|
||||
public const string GroupName = nameof(ColumnVisibilityToggle);
|
||||
|
||||
public static readonly string
|
||||
Active = $"{GroupName}{VisualStates.Active}",
|
||||
Inactive = $"{GroupName}{VisualStates.Inactive}";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,375 +1,379 @@
|
||||
using bts.api;
|
||||
using bts.collections;
|
||||
using bts.settings;
|
||||
using bts.shell.pnl;
|
||||
using bts.spark;
|
||||
using bts.utils;
|
||||
using mbts;
|
||||
using mbts.config;
|
||||
using mbts.persist;
|
||||
using mbts.spark;
|
||||
using mbts.ui;
|
||||
using mbts.ui.controls;
|
||||
using mbts.ui.settings;
|
||||
using mbts.ui.spark;
|
||||
using mbts.ui.utility;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Collections.Specialized;
|
||||
using System.ComponentModel;
|
||||
using System.ComponentModel.Composition;
|
||||
using System.Linq;
|
||||
using System.Reactive.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Media;
|
||||
|
||||
namespace bts.shell {
|
||||
class PNLWindowFactory : ShellWindowFactory<PNLSaveState> {
|
||||
const string _window_type = "PNL", _window_title = "PNL";
|
||||
readonly IBus _bus;
|
||||
readonly OrdersModel _orders_model;
|
||||
readonly UserSettingsHub _settings_hub;
|
||||
readonly PNLConfig _pnl_conf;
|
||||
readonly IShellWindowThreadIDProvider _thread_id_provider;
|
||||
|
||||
public PNLWindowFactory(IShellModule owner, IBus bus, UserSettingsHub settings_hub, OrdersModel orders_model) : base(owner, _window_type) {
|
||||
_bus = bus;
|
||||
_orders_model = orders_model;
|
||||
_settings_hub = settings_hub;
|
||||
_pnl_conf = new PNLConfig(Configuration.get<bts.config.PNLOptions>());
|
||||
_thread_id_provider = ShellWindowThreadIDFactory.create(_window_type, Configuration.get<bts.config.MultiThreadedWindows>().pnl);
|
||||
_dispose = bus.subscribe(SparkTags.ShowPNLTag).OnDispatcher().Subscribe(_show);
|
||||
}
|
||||
|
||||
public override Task<IShellWindow> load(PNLSaveState state) =>
|
||||
Task.FromResult(_create(state.settings));
|
||||
|
||||
void _show(PNLSettings settings) =>
|
||||
_bus.post(SparkTags.ShowWindow, _create(settings));
|
||||
|
||||
IShellWindow _create(PNLSettings settings) {
|
||||
var settings_manager = _settings_hub.create_pnl_settings_manager(settings);
|
||||
// Need to keep a local copy of the columns, since they are defined in xaml (not the viewmodel), and get_save_state may be called on a non-dispatcher thread.
|
||||
// If that happens, we would be unable to access _grid.Columns, and thus unable to get the current column settings.
|
||||
// Use an ImmutableList to avoid any threading issues when responding to CollectionChanged events on the dispatcher thread.
|
||||
ImmutableList<DIYGrid.ColumnBase> cached_columns = ImmutableList<DIYGrid.ColumnBase>.Empty;
|
||||
return SparkWindow<PNLSaveState>.create(_window_type, _window_title, _bus, this, Icons.Accounting, _thread_id_provider.next_id(), () => {
|
||||
var vm = new PNLVM(_bus, _orders_model, settings ?? PNLSettings.Default);
|
||||
var content = new PNLView {
|
||||
DataContext = vm
|
||||
};
|
||||
vm.sorting_changed += sh => {
|
||||
try {
|
||||
DIYGridSortHelper.set_sort_directions(sh, content._grid.Columns);
|
||||
} catch { }
|
||||
};
|
||||
vm.SubscribeDisposed(
|
||||
DIYGridSortHelper.observe_active_comparers<PNLView.Columns, PNLRowVM>(content._grid, PNLVM.get_column_comparer)
|
||||
.OnDispatcher()
|
||||
.Subscribe(sh => vm.sorting_hierarchy = sh.Select(s => (s.key, s.direction)).ToList()));
|
||||
cached_columns = ImmutableList<DIYGrid.ColumnBase>.Empty.AddRange(content._grid.Columns);
|
||||
|
||||
Func<PNLSaveState> get_save_state = () => {
|
||||
var cols = cached_columns.ToArray(c => c.create());
|
||||
var filters = vm.get_filters_states();
|
||||
var sorting_criterion = vm.sorting_hierarchy.Select(tup => new SortingCriteria(tup.key.ToString(), tup.direction == ListSortDirection.Ascending)).ToArray();
|
||||
var calc_type = vm.calculation_type;
|
||||
var state = settings_manager.instance_settings;
|
||||
if (state == null) {
|
||||
// don't use defaults if the user manipulated the window state outside of the settings dialog (e.g., resized column(s) via drag, or changed sort/filter)
|
||||
var defaults = settings_manager.default_settings;
|
||||
if (calc_type == defaults.calculation_type &&
|
||||
defaults.sorting_criterion.SequenceEqual(sorting_criterion) &&
|
||||
defaults.grid.columns.SequenceEqual(cols) && defaults.filters.safe_equals(filters)) {
|
||||
// the user's current state still matches the defaults (i.e., no outside manipulation was performed); return null so this window will use the latest default settings when restored
|
||||
return new PNLSaveState { use_defaults = true, settings = null };
|
||||
}
|
||||
state = defaults;
|
||||
}
|
||||
// assign latest values, as they may have be modified outside of the settings dialog (in which case the settings manager would be out of date)
|
||||
state = state.with(
|
||||
sorting_criterion,
|
||||
vm.anchors,
|
||||
filters,
|
||||
state.grid.with(columns: cols, background: vm.grid_background_color.rgb_color()),
|
||||
calc_type,
|
||||
vm.header_row_display);
|
||||
return new PNLSaveState { use_defaults = false, settings = state };
|
||||
};
|
||||
IEnumerable<SparkCommand> commands = new[] {
|
||||
new SparkCommandCommand("Collapse All", Icons.RightArrow, new DelegateCommand(() => vm.set_all_expanded(false))),
|
||||
new SparkCommandCommand("Expand All", Icons.DownArrow, new DelegateCommand(() => vm.set_all_expanded(true))),
|
||||
new SparkCommandCommand("Play", Icons.Play, new DelegateCommand(() => vm.is_paused = false)) {
|
||||
show_in_context_menu = _pnl_conf.show_pause_resume,
|
||||
show_in_toolbar = _pnl_conf.show_pause_resume
|
||||
},
|
||||
new SparkCommandCommand("Pause", Icons.Pause, new DelegateCommand(() => vm.is_paused = true)) {
|
||||
show_in_context_menu = _pnl_conf.show_pause_resume,
|
||||
show_in_toolbar = _pnl_conf.show_pause_resume
|
||||
},
|
||||
SparkCommands.window_settings_command(new DialogCommand(() => {
|
||||
settings_manager.instance_settings = get_save_state().settings;
|
||||
var dialog_vm = new ShellSettingsDialogVM<PNLSettings, PNLSettingsVM>(
|
||||
settings_manager,
|
||||
us => us.pnl,
|
||||
UserSettings.FileExtensions.PNL,
|
||||
s => new PNLSettingsVM(s));
|
||||
var dialog = new ShellSettingsDialog { DataContext = dialog_vm };
|
||||
dialog_vm.RequestClose += on_request_close;
|
||||
return dialog;
|
||||
void on_request_close(object sender, EventArgs e) =>
|
||||
WindowUtilities.force_close(dialog);
|
||||
}))
|
||||
};
|
||||
IDisposable dispose = Disposable.Create(
|
||||
settings_manager.subscribe_current().OnDispatcher().Subscribe(s => {
|
||||
vm.load_from(s);
|
||||
var col_setting_map = s.grid.columns.ToDictionary(c => c.id);
|
||||
var fallback_family = new FontFamily(s.grid.font_family);
|
||||
// group
|
||||
var group_col_setting_map = s.group_columns.ToDictionary(c => c.id);
|
||||
var group_appearance = s.group_cell_appearance.to_appearance();
|
||||
// header
|
||||
var header_col_setting_map = s.header_columns.ToDictionary(c => c.id);
|
||||
var header_appearance = s.header_cell_appearance.to_appearance();
|
||||
//
|
||||
foreach (var c in content._grid.Columns.OfType<PNLColumn>()) {
|
||||
if (col_setting_map.TryGetValue(c.id, out var cs)) {
|
||||
c.load_from(cs);
|
||||
}
|
||||
if (group_col_setting_map.TryGetValue(c.id, out var gcs)) {
|
||||
c.load_group_params(
|
||||
group_appearance.font_size,
|
||||
gcs.font_weight_override ?? group_appearance.font_weight,
|
||||
gcs.font_color_override ?? group_appearance.foreground,
|
||||
group_appearance.font_family);
|
||||
}
|
||||
if (header_col_setting_map.TryGetValue(c.id, out var hcs)) {
|
||||
c.load_header_params(
|
||||
header_appearance.font_size,
|
||||
hcs.font_weight_override ?? header_appearance.font_weight,
|
||||
hcs.font_color_override ?? header_appearance.foreground,
|
||||
header_appearance.font_family);
|
||||
}
|
||||
}
|
||||
CollectionHelper.sync_with_never_duplicate(content._grid.Columns, s.grid.columns, cs => {
|
||||
throw new InvalidOperationException();
|
||||
}, c => c.id.ToLower(), cs => cs.id.ToLower());
|
||||
}),
|
||||
content._grid.Columns.Subscribe((s, e) => {
|
||||
switch (e.Action) {
|
||||
case NotifyCollectionChangedAction.Add: add_new(); break;
|
||||
case NotifyCollectionChangedAction.Remove: remove_old(); break;
|
||||
case NotifyCollectionChangedAction.Replace:
|
||||
case NotifyCollectionChangedAction.Move:
|
||||
remove_old();
|
||||
add_new();
|
||||
break;
|
||||
case NotifyCollectionChangedAction.Reset:
|
||||
cached_columns = ImmutableList<DIYGrid.ColumnBase>.Empty.AddRange(content._grid.Columns);
|
||||
break;
|
||||
}
|
||||
void add_new() =>
|
||||
cached_columns = e.NewStartingIndex >= 0 ?
|
||||
cached_columns.InsertRange(e.NewStartingIndex, e.NewItems.OfType<DIYGrid.ColumnBase>()) :
|
||||
cached_columns.AddRange(e.NewItems.OfType<DIYGrid.ColumnBase>());
|
||||
|
||||
void remove_old() =>
|
||||
cached_columns = e.OldStartingIndex >= 0 ?
|
||||
cached_columns.RemoveRange(e.OldStartingIndex, e.OldItems.Count) :
|
||||
cached_columns.RemoveRange(e.OldItems.OfType<DIYGrid.ColumnBase>());
|
||||
}),
|
||||
Disposable.wrap_collection(commands.OfType<IDisposable>()),
|
||||
vm);
|
||||
return (content, commands, get_save_state, dispose);
|
||||
},
|
||||
min_width_offset: SystemParameters.HorizontalScrollBarButtonWidth + 8,
|
||||
min_height_offset: 60);
|
||||
}
|
||||
|
||||
readonly struct PNLConfig {
|
||||
public readonly bool
|
||||
enable_animations,
|
||||
show_pause_resume;
|
||||
|
||||
public PNLConfig(bts.config.PNLOptions options) {
|
||||
enable_animations = options.enable_animations;
|
||||
show_pause_resume = options.show_pause_resume;
|
||||
}
|
||||
}
|
||||
|
||||
[Export(typeof(IPNLSettingsMigrationRule))]
|
||||
class AddMissingAnimationsMigrationRule : IPNLSettingsMigrationRule {
|
||||
public bool apply(PNLSettings settings, out PNLSettings new_settings) {
|
||||
if (settings != null && (
|
||||
_apply(ref settings, settings.animations, PNLSettings.default_animations, (s, anims) => s.with(animations: anims)) |
|
||||
_apply(ref settings, settings.group_header_animations, PNLSettings.default_group_header_animations, (s, anims) => s.with(group_header_animations: anims)) |
|
||||
_apply(ref settings, settings.header_row_animations, PNLSettings.default_header_row_animations, (s, anims) => s.with(header_row_animations: anims)))) {
|
||||
//
|
||||
new_settings = settings;
|
||||
return true;
|
||||
}
|
||||
new_settings = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool _apply(ref PNLSettings settings, IEnumerable<PNLAnimationTriggerData> actual_triggers, IEnumerable<PNLAnimationTriggerData> default_triggers, Func<PNLSettings, PNLAnimationTriggerData[], PNLSettings> set_triggers) {
|
||||
if (_apply(actual_triggers, default_triggers, out var result)) {
|
||||
settings = set_triggers(settings, actual_triggers.Concat(result).ToArray());
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool _apply(IEnumerable<PNLAnimationTriggerData> actual_triggers, IEnumerable<PNLAnimationTriggerData> default_triggers, out IEnumerable<PNLAnimationTriggerData> result) {
|
||||
var default_trigger_map = default_triggers.ToDictionary(t => t.expression);
|
||||
var actual_trigger_map = actual_triggers.ToImmutableDictionary(t => t.expression);
|
||||
var snapshot = actual_trigger_map;
|
||||
foreach (var kv in snapshot) {
|
||||
if (default_trigger_map.TryGetValue(kv.Key, out var default_anims)) {
|
||||
// assume 1 animation per column
|
||||
var actual_col_anims = kv.Value.animations.ToDictionaryCrushed(a => a.column);
|
||||
var default_col_anims = default_anims.animations.ToDictionaryCrushed(a => a.column);
|
||||
actual_col_anims.Difference(default_col_anims,
|
||||
out var exclusive_actual,
|
||||
out var exclusive_defaults,
|
||||
out var diff);
|
||||
var new_col_anims = exclusive_defaults.ToList();
|
||||
if (new_col_anims.Count > 0) {
|
||||
// add the original col animations to the new ones (combine them)
|
||||
new_col_anims.AddRange(actual_col_anims);
|
||||
actual_trigger_map = actual_trigger_map.SetItem(kv.Key, kv.Value.with(animations: new_col_anims.Select(col_anim => col_anim.Value).ToArray()));
|
||||
}
|
||||
}
|
||||
}
|
||||
foreach (var kv in default_trigger_map) {
|
||||
if (!actual_trigger_map.ContainsKey(kv.Key))
|
||||
actual_trigger_map = actual_trigger_map.Add(kv.Key, kv.Value);
|
||||
}
|
||||
if (actual_trigger_map != snapshot) {
|
||||
result = actual_trigger_map.Values;
|
||||
return true;
|
||||
}
|
||||
result = null;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
[Export(typeof(IPNLSettingsMigrationRule))]
|
||||
class AddMissingColumnsMigrationRule : IPNLSettingsMigrationRule {
|
||||
public bool apply(PNLSettings settings, out PNLSettings new_settings) {
|
||||
if (settings != null) {
|
||||
var defaults = PNLSettings.Default;
|
||||
if (_apply(ref settings, settings.grid.columns, defaults.grid.columns, (s, cols) => s.with(grid: s.grid.with(cols))) |
|
||||
_apply(ref settings, settings.group_columns, defaults.group_columns, (s, cols) => s.with(group_columns: cols)) |
|
||||
_apply(ref settings, settings.header_columns, defaults.header_columns, (s, cols) => s.with(header_columns: cols))) {
|
||||
new_settings = settings;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
new_settings = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool _apply(ref PNLSettings settings, IEnumerable<GridColumnSettings> actual_columns, IEnumerable<GridColumnSettings> default_columns, Func<PNLSettings, IEnumerable<GridColumnSettings>, PNLSettings> set_columns) {
|
||||
if (GridSettingsHelper.add_missing_columns(actual_columns, default_columns, out var new_cols)) {
|
||||
settings = set_columns(settings, new_cols);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
[Export(typeof(IPNLSettingsMigrationRule))]
|
||||
class RemoveColumnsMigrationRule : IPNLSettingsMigrationRule {
|
||||
public bool apply(PNLSettings settings, out PNLSettings new_settings) {
|
||||
if (settings != null) {
|
||||
if (_apply(ref settings, settings.grid.columns, (s, cols) => s.with(grid: s.grid.with(cols))) |
|
||||
_apply(ref settings, settings.group_columns, (s, cols) => s.with(group_columns: cols)) |
|
||||
_apply(ref settings, settings.header_columns, (s, cols) => s.with(header_columns: cols))) {
|
||||
new_settings = settings;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
new_settings = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
bool _apply(ref PNLSettings settings, IEnumerable<GridColumnSettings> columns, Func<PNLSettings, IEnumerable<GridColumnSettings>, PNLSettings> set_columns) {
|
||||
if (_apply(columns, out var new_cols)) {
|
||||
settings = set_columns(settings, new_cols);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool _apply(IEnumerable<GridColumnSettings> columns, out IEnumerable<GridColumnSettings> result) {
|
||||
var col_list = columns.ToList();
|
||||
int orig_count = col_list.Count;
|
||||
col_list.RemoveWhere(c => !Enum.TryParse<PNLView.Columns>(c.id, true, out var _));
|
||||
if (col_list.Count < orig_count) {
|
||||
result = col_list;
|
||||
return true;
|
||||
}
|
||||
result = null;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
[Export(typeof(IPNLSettingsMigrationRule))]
|
||||
class RemoveColumnAnimationsMigrationRule : IPNLSettingsMigrationRule {
|
||||
public bool apply(PNLSettings settings, out PNLSettings new_settings) {
|
||||
if (settings != null) {
|
||||
if (_apply(ref settings, settings.animations, (s, t) => s.with(animations: t)) |
|
||||
_apply(ref settings, settings.group_header_animations, (s, t) => s.with(group_header_animations: t)) |
|
||||
_apply(ref settings, settings.header_row_animations, (s, t) => s.with(header_row_animations: t))) {
|
||||
new_settings = settings;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
new_settings = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
bool _apply(ref PNLSettings settings, IEnumerable<PNLAnimationTriggerData> triggers, Func<PNLSettings, IEnumerable<PNLAnimationTriggerData>, PNLSettings> set_triggers) {
|
||||
if (_apply(triggers, out var new_triggers)) {
|
||||
settings = set_triggers(settings, new_triggers);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool _apply(IEnumerable<PNLAnimationTriggerData> triggers, out IEnumerable<PNLAnimationTriggerData> result) {
|
||||
var trigger_map = triggers.ToImmutableDictionary(t => t.expression);
|
||||
var snapshot = trigger_map;
|
||||
foreach (var t in trigger_map.Values) {
|
||||
var anims = t.animations.ToList();
|
||||
int orig_count = anims.Count;
|
||||
anims.RemoveWhere(a => !Enum.IsDefined(typeof(PNLView.Columns), a.column));
|
||||
if (anims.Count < orig_count)
|
||||
trigger_map = trigger_map.SetItem(t.expression, t.with(animations: anims.ToArray()));
|
||||
}
|
||||
if (trigger_map != snapshot) {
|
||||
result = trigger_map.Values;
|
||||
return true;
|
||||
}
|
||||
result = null;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
[Export(typeof(IPNLSettingsMigrationRule))]
|
||||
class EnableGridBackgroundColorMigrationRule : IPNLSettingsMigrationRule {
|
||||
public bool apply(PNLSettings settings, out PNLSettings new_settings) {
|
||||
if (settings != null && !settings.grid.enable_background_color) {
|
||||
new_settings = settings.with(grid: settings.grid.with(enable_background_color: true));
|
||||
return true;
|
||||
}
|
||||
new_settings = null;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
using bts.api;
|
||||
using bts.collections;
|
||||
using bts.settings;
|
||||
using bts.shell.pnl;
|
||||
using bts.spark;
|
||||
using bts.utils;
|
||||
using mbts;
|
||||
using mbts.config;
|
||||
using mbts.persist;
|
||||
using mbts.spark;
|
||||
using mbts.ui;
|
||||
using mbts.ui.controls;
|
||||
using mbts.ui.settings;
|
||||
using mbts.ui.spark;
|
||||
using mbts.ui.utility;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Collections.Specialized;
|
||||
using System.ComponentModel;
|
||||
using System.ComponentModel.Composition;
|
||||
using System.Linq;
|
||||
using System.Reactive.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Media;
|
||||
|
||||
namespace bts.shell {
|
||||
class PNLWindowFactory : ShellWindowFactory<PNLSaveState> {
|
||||
const string _window_type = "PNL", _window_title = "PNL";
|
||||
readonly IBus _bus;
|
||||
readonly OrdersModel _orders_model;
|
||||
readonly UserSettingsHub _settings_hub;
|
||||
readonly PNLConfig _pnl_conf;
|
||||
readonly IShellWindowThreadIDProvider _thread_id_provider;
|
||||
|
||||
public PNLWindowFactory(IShellModule owner, IBus bus, UserSettingsHub settings_hub, OrdersModel orders_model) : base(owner, _window_type) {
|
||||
_bus = bus;
|
||||
_orders_model = orders_model;
|
||||
_settings_hub = settings_hub;
|
||||
_pnl_conf = new PNLConfig(Configuration.get<bts.config.PNLOptions>());
|
||||
_thread_id_provider = ShellWindowThreadIDFactory.create(_window_type, Configuration.get<bts.config.MultiThreadedWindows>().pnl);
|
||||
_dispose = bus.subscribe(SparkTags.ShowPNLTag).OnDispatcher().Subscribe(_show);
|
||||
}
|
||||
|
||||
public override Task<IShellWindow> load(PNLSaveState state) =>
|
||||
Task.FromResult(_create(state.settings));
|
||||
|
||||
void _show(PNLSettings settings) =>
|
||||
_bus.post(SparkTags.ShowWindow, _create(settings));
|
||||
|
||||
IShellWindow _create(PNLSettings settings) {
|
||||
var settings_manager = _settings_hub.create_pnl_settings_manager(settings);
|
||||
// Need to keep a local copy of the columns, since they are defined in xaml (not the viewmodel), and get_save_state may be called on a non-dispatcher thread.
|
||||
// If that happens, we would be unable to access _grid.Columns, and thus unable to get the current column settings.
|
||||
// Use an ImmutableList to avoid any threading issues when responding to CollectionChanged events on the dispatcher thread.
|
||||
ImmutableList<DIYGrid.ColumnBase> cached_columns = ImmutableList<DIYGrid.ColumnBase>.Empty;
|
||||
SparkWindow<PNLSaveState> main_win = null;
|
||||
main_win = SparkWindow<PNLSaveState>.create(_window_type, _window_title, _bus, this, Icons.Accounting, _thread_id_provider.next_id(), () => {
|
||||
var vm = new PNLVM(_bus, _orders_model, settings ?? PNLSettings.Default);
|
||||
var content = new PNLView {
|
||||
DataContext = vm
|
||||
};
|
||||
vm.sorting_changed += sh => {
|
||||
try {
|
||||
DIYGridSortHelper.set_sort_directions(sh, content._grid.Columns);
|
||||
} catch { }
|
||||
};
|
||||
vm.SubscribeDisposed(
|
||||
DIYGridSortHelper.observe_active_comparers<PNLView.Columns, PNLRowVM>(content._grid, PNLVM.get_column_comparer)
|
||||
.OnDispatcher()
|
||||
.Subscribe(sh => vm.sorting_hierarchy = sh.Select(s => (s.key, s.direction)).ToList()));
|
||||
cached_columns = ImmutableList<DIYGrid.ColumnBase>.Empty.AddRange(content._grid.Columns);
|
||||
|
||||
Func<PNLSaveState> get_save_state = () => {
|
||||
var cols = cached_columns.ToArray(c => c.create());
|
||||
var filters = vm.get_filters_states();
|
||||
var sorting_criterion = vm.sorting_hierarchy.Select(tup => new SortingCriteria(tup.key.ToString(), tup.direction == ListSortDirection.Ascending)).ToArray();
|
||||
var calc_type = vm.calculation_type;
|
||||
var state = settings_manager.instance_settings;
|
||||
if (state == null) {
|
||||
// don't use defaults if the user manipulated the window state outside of the settings dialog (e.g., resized column(s) via drag, or changed sort/filter)
|
||||
var defaults = settings_manager.default_settings;
|
||||
if (calc_type == defaults.calculation_type &&
|
||||
defaults.sorting_criterion.SequenceEqual(sorting_criterion) &&
|
||||
defaults.grid.columns.SequenceEqual(cols) && defaults.filters.safe_equals(filters)) {
|
||||
// the user's current state still matches the defaults (i.e., no outside manipulation was performed); return null so this window will use the latest default settings when restored
|
||||
return new PNLSaveState { use_defaults = true, settings = null };
|
||||
}
|
||||
state = defaults;
|
||||
}
|
||||
// assign latest values, as they may have be modified outside of the settings dialog (in which case the settings manager would be out of date)
|
||||
state = state.with(
|
||||
sorting_criterion,
|
||||
vm.anchors,
|
||||
filters,
|
||||
state.grid.with(columns: cols, background: vm.grid_background_color.rgb_color()),
|
||||
calc_type,
|
||||
vm.header_row_display);
|
||||
return new PNLSaveState { use_defaults = false, settings = state };
|
||||
};
|
||||
IEnumerable<SparkCommand> commands = new[] {
|
||||
new SparkCommandCommand("Collapse All", Icons.RightArrow, new DelegateCommand(() => vm.set_all_expanded(false))),
|
||||
new SparkCommandCommand("Expand All", Icons.DownArrow, new DelegateCommand(() => vm.set_all_expanded(true))),
|
||||
new SparkCommandCommand("Play", Icons.Play, new DelegateCommand(() => vm.is_paused = false)) {
|
||||
show_in_context_menu = _pnl_conf.show_pause_resume,
|
||||
show_in_toolbar = _pnl_conf.show_pause_resume
|
||||
},
|
||||
new SparkCommandCommand("Pause", Icons.Pause, new DelegateCommand(() => vm.is_paused = true)) {
|
||||
show_in_context_menu = _pnl_conf.show_pause_resume,
|
||||
show_in_toolbar = _pnl_conf.show_pause_resume
|
||||
},
|
||||
SparkCommands.window_settings_command(new DialogCommand(() => {
|
||||
settings_manager.instance_settings = get_save_state().settings;
|
||||
var dialog_vm = new ShellSettingsDialogVM<PNLSettings, PNLSettingsVM>(
|
||||
settings_manager,
|
||||
us => us.pnl,
|
||||
UserSettings.FileExtensions.PNL,
|
||||
s => new PNLSettingsVM(s));
|
||||
var dialog = new ShellSettingsDialog { DataContext = dialog_vm };
|
||||
if (main_win != null)
|
||||
dialog_vm.SubscribeDisposed(main_win.literal_title_source.OnDispatcher().Subscribe(t => dialog_vm.title = t));
|
||||
dialog_vm.RequestClose += on_request_close;
|
||||
return dialog;
|
||||
void on_request_close(object sender, EventArgs e) =>
|
||||
WindowUtilities.force_close(dialog);
|
||||
}))
|
||||
};
|
||||
IDisposable dispose = Disposable.Create(
|
||||
settings_manager.subscribe_current().OnDispatcher().Subscribe(s => {
|
||||
vm.load_from(s);
|
||||
var col_setting_map = s.grid.columns.ToDictionary(c => c.id);
|
||||
var fallback_family = new FontFamily(s.grid.font_family);
|
||||
// group
|
||||
var group_col_setting_map = s.group_columns.ToDictionary(c => c.id);
|
||||
var group_appearance = s.group_cell_appearance.to_appearance();
|
||||
// header
|
||||
var header_col_setting_map = s.header_columns.ToDictionary(c => c.id);
|
||||
var header_appearance = s.header_cell_appearance.to_appearance();
|
||||
//
|
||||
foreach (var c in content._grid.Columns.OfType<PNLColumn>()) {
|
||||
if (col_setting_map.TryGetValue(c.id, out var cs)) {
|
||||
c.load_from(cs);
|
||||
}
|
||||
if (group_col_setting_map.TryGetValue(c.id, out var gcs)) {
|
||||
c.load_group_params(
|
||||
group_appearance.font_size,
|
||||
gcs.font_weight_override ?? group_appearance.font_weight,
|
||||
gcs.font_color_override ?? group_appearance.foreground,
|
||||
group_appearance.font_family);
|
||||
}
|
||||
if (header_col_setting_map.TryGetValue(c.id, out var hcs)) {
|
||||
c.load_header_params(
|
||||
header_appearance.font_size,
|
||||
hcs.font_weight_override ?? header_appearance.font_weight,
|
||||
hcs.font_color_override ?? header_appearance.foreground,
|
||||
header_appearance.font_family);
|
||||
}
|
||||
}
|
||||
CollectionHelper.sync_with_never_duplicate(content._grid.Columns, s.grid.columns, cs => {
|
||||
throw new InvalidOperationException();
|
||||
}, c => c.id.ToLower(), cs => cs.id.ToLower());
|
||||
}),
|
||||
content._grid.Columns.Subscribe((s, e) => {
|
||||
switch (e.Action) {
|
||||
case NotifyCollectionChangedAction.Add: add_new(); break;
|
||||
case NotifyCollectionChangedAction.Remove: remove_old(); break;
|
||||
case NotifyCollectionChangedAction.Replace:
|
||||
case NotifyCollectionChangedAction.Move:
|
||||
remove_old();
|
||||
add_new();
|
||||
break;
|
||||
case NotifyCollectionChangedAction.Reset:
|
||||
cached_columns = ImmutableList<DIYGrid.ColumnBase>.Empty.AddRange(content._grid.Columns);
|
||||
break;
|
||||
}
|
||||
void add_new() =>
|
||||
cached_columns = e.NewStartingIndex >= 0 ?
|
||||
cached_columns.InsertRange(e.NewStartingIndex, e.NewItems.OfType<DIYGrid.ColumnBase>()) :
|
||||
cached_columns.AddRange(e.NewItems.OfType<DIYGrid.ColumnBase>());
|
||||
|
||||
void remove_old() =>
|
||||
cached_columns = e.OldStartingIndex >= 0 ?
|
||||
cached_columns.RemoveRange(e.OldStartingIndex, e.OldItems.Count) :
|
||||
cached_columns.RemoveRange(e.OldItems.OfType<DIYGrid.ColumnBase>());
|
||||
}),
|
||||
Disposable.wrap_collection(commands.OfType<IDisposable>()),
|
||||
vm);
|
||||
return (content, commands, get_save_state, dispose);
|
||||
},
|
||||
min_width_offset: SystemParameters.HorizontalScrollBarButtonWidth + 8,
|
||||
min_height_offset: 60);
|
||||
return main_win;
|
||||
}
|
||||
|
||||
readonly struct PNLConfig {
|
||||
public readonly bool
|
||||
enable_animations,
|
||||
show_pause_resume;
|
||||
|
||||
public PNLConfig(bts.config.PNLOptions options) {
|
||||
enable_animations = options.enable_animations;
|
||||
show_pause_resume = options.show_pause_resume;
|
||||
}
|
||||
}
|
||||
|
||||
[Export(typeof(IPNLSettingsMigrationRule))]
|
||||
class AddMissingAnimationsMigrationRule : IPNLSettingsMigrationRule {
|
||||
public bool apply(PNLSettings settings, out PNLSettings new_settings) {
|
||||
if (settings != null && (
|
||||
_apply(ref settings, settings.animations, PNLSettings.default_animations, (s, anims) => s.with(animations: anims)) |
|
||||
_apply(ref settings, settings.group_header_animations, PNLSettings.default_group_header_animations, (s, anims) => s.with(group_header_animations: anims)) |
|
||||
_apply(ref settings, settings.header_row_animations, PNLSettings.default_header_row_animations, (s, anims) => s.with(header_row_animations: anims)))) {
|
||||
//
|
||||
new_settings = settings;
|
||||
return true;
|
||||
}
|
||||
new_settings = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool _apply(ref PNLSettings settings, IEnumerable<PNLAnimationTriggerData> actual_triggers, IEnumerable<PNLAnimationTriggerData> default_triggers, Func<PNLSettings, PNLAnimationTriggerData[], PNLSettings> set_triggers) {
|
||||
if (_apply(actual_triggers, default_triggers, out var result)) {
|
||||
settings = set_triggers(settings, actual_triggers.Concat(result).ToArray());
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool _apply(IEnumerable<PNLAnimationTriggerData> actual_triggers, IEnumerable<PNLAnimationTriggerData> default_triggers, out IEnumerable<PNLAnimationTriggerData> result) {
|
||||
var default_trigger_map = default_triggers.ToDictionary(t => t.expression);
|
||||
var actual_trigger_map = actual_triggers.ToImmutableDictionary(t => t.expression);
|
||||
var snapshot = actual_trigger_map;
|
||||
foreach (var kv in snapshot) {
|
||||
if (default_trigger_map.TryGetValue(kv.Key, out var default_anims)) {
|
||||
// assume 1 animation per column
|
||||
var actual_col_anims = kv.Value.animations.ToDictionaryCrushed(a => a.column);
|
||||
var default_col_anims = default_anims.animations.ToDictionaryCrushed(a => a.column);
|
||||
actual_col_anims.Difference(default_col_anims,
|
||||
out var exclusive_actual,
|
||||
out var exclusive_defaults,
|
||||
out var diff);
|
||||
var new_col_anims = exclusive_defaults.ToList();
|
||||
if (new_col_anims.Count > 0) {
|
||||
// add the original col animations to the new ones (combine them)
|
||||
new_col_anims.AddRange(actual_col_anims);
|
||||
actual_trigger_map = actual_trigger_map.SetItem(kv.Key, kv.Value.with(animations: new_col_anims.Select(col_anim => col_anim.Value).ToArray()));
|
||||
}
|
||||
}
|
||||
}
|
||||
foreach (var kv in default_trigger_map) {
|
||||
if (!actual_trigger_map.ContainsKey(kv.Key))
|
||||
actual_trigger_map = actual_trigger_map.Add(kv.Key, kv.Value);
|
||||
}
|
||||
if (actual_trigger_map != snapshot) {
|
||||
result = actual_trigger_map.Values;
|
||||
return true;
|
||||
}
|
||||
result = null;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
[Export(typeof(IPNLSettingsMigrationRule))]
|
||||
class AddMissingColumnsMigrationRule : IPNLSettingsMigrationRule {
|
||||
public bool apply(PNLSettings settings, out PNLSettings new_settings) {
|
||||
if (settings != null) {
|
||||
var defaults = PNLSettings.Default;
|
||||
if (_apply(ref settings, settings.grid.columns, defaults.grid.columns, (s, cols) => s.with(grid: s.grid.with(cols))) |
|
||||
_apply(ref settings, settings.group_columns, defaults.group_columns, (s, cols) => s.with(group_columns: cols)) |
|
||||
_apply(ref settings, settings.header_columns, defaults.header_columns, (s, cols) => s.with(header_columns: cols))) {
|
||||
new_settings = settings;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
new_settings = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool _apply(ref PNLSettings settings, IEnumerable<GridColumnSettings> actual_columns, IEnumerable<GridColumnSettings> default_columns, Func<PNLSettings, IEnumerable<GridColumnSettings>, PNLSettings> set_columns) {
|
||||
if (GridSettingsHelper.add_missing_columns(actual_columns, default_columns, out var new_cols)) {
|
||||
settings = set_columns(settings, new_cols);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
[Export(typeof(IPNLSettingsMigrationRule))]
|
||||
class RemoveColumnsMigrationRule : IPNLSettingsMigrationRule {
|
||||
public bool apply(PNLSettings settings, out PNLSettings new_settings) {
|
||||
if (settings != null) {
|
||||
if (_apply(ref settings, settings.grid.columns, (s, cols) => s.with(grid: s.grid.with(cols))) |
|
||||
_apply(ref settings, settings.group_columns, (s, cols) => s.with(group_columns: cols)) |
|
||||
_apply(ref settings, settings.header_columns, (s, cols) => s.with(header_columns: cols))) {
|
||||
new_settings = settings;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
new_settings = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
bool _apply(ref PNLSettings settings, IEnumerable<GridColumnSettings> columns, Func<PNLSettings, IEnumerable<GridColumnSettings>, PNLSettings> set_columns) {
|
||||
if (_apply(columns, out var new_cols)) {
|
||||
settings = set_columns(settings, new_cols);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool _apply(IEnumerable<GridColumnSettings> columns, out IEnumerable<GridColumnSettings> result) {
|
||||
var col_list = columns.ToList();
|
||||
int orig_count = col_list.Count;
|
||||
col_list.RemoveWhere(c => !Enum.TryParse<PNLView.Columns>(c.id, true, out var _));
|
||||
if (col_list.Count < orig_count) {
|
||||
result = col_list;
|
||||
return true;
|
||||
}
|
||||
result = null;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
[Export(typeof(IPNLSettingsMigrationRule))]
|
||||
class RemoveColumnAnimationsMigrationRule : IPNLSettingsMigrationRule {
|
||||
public bool apply(PNLSettings settings, out PNLSettings new_settings) {
|
||||
if (settings != null) {
|
||||
if (_apply(ref settings, settings.animations, (s, t) => s.with(animations: t)) |
|
||||
_apply(ref settings, settings.group_header_animations, (s, t) => s.with(group_header_animations: t)) |
|
||||
_apply(ref settings, settings.header_row_animations, (s, t) => s.with(header_row_animations: t))) {
|
||||
new_settings = settings;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
new_settings = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
bool _apply(ref PNLSettings settings, IEnumerable<PNLAnimationTriggerData> triggers, Func<PNLSettings, IEnumerable<PNLAnimationTriggerData>, PNLSettings> set_triggers) {
|
||||
if (_apply(triggers, out var new_triggers)) {
|
||||
settings = set_triggers(settings, new_triggers);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool _apply(IEnumerable<PNLAnimationTriggerData> triggers, out IEnumerable<PNLAnimationTriggerData> result) {
|
||||
var trigger_map = triggers.ToImmutableDictionary(t => t.expression);
|
||||
var snapshot = trigger_map;
|
||||
foreach (var t in trigger_map.Values) {
|
||||
var anims = t.animations.ToList();
|
||||
int orig_count = anims.Count;
|
||||
anims.RemoveWhere(a => !Enum.IsDefined(typeof(PNLView.Columns), a.column));
|
||||
if (anims.Count < orig_count)
|
||||
trigger_map = trigger_map.SetItem(t.expression, t.with(animations: anims.ToArray()));
|
||||
}
|
||||
if (trigger_map != snapshot) {
|
||||
result = trigger_map.Values;
|
||||
return true;
|
||||
}
|
||||
result = null;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
[Export(typeof(IPNLSettingsMigrationRule))]
|
||||
class EnableGridBackgroundColorMigrationRule : IPNLSettingsMigrationRule {
|
||||
public bool apply(PNLSettings settings, out PNLSettings new_settings) {
|
||||
if (settings != null && !settings.grid.enable_background_color) {
|
||||
new_settings = settings.with(grid: settings.grid.with(enable_background_color: true));
|
||||
return true;
|
||||
}
|
||||
new_settings = null;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
using mbts.ui.viewmodels;
|
||||
|
||||
namespace bts.shell.pnl {
|
||||
class PNLAnchorHierarchyVM : ItemOrderParameterVM<PNLView.Columns> {
|
||||
public PNLAnchorHierarchyVM()
|
||||
: base("", PNLSettings.anchorable_columns) { }
|
||||
}
|
||||
}
|
||||
@@ -1,122 +1,88 @@
|
||||
using bts.proto;
|
||||
using bts.collections;
|
||||
using bts.proto;
|
||||
using mbts;
|
||||
using mbts.spark;
|
||||
using mbts.ui;
|
||||
using mbts.ui.controls;
|
||||
using mbts.ui.settings;
|
||||
using mbts.ui.shell;
|
||||
using mbts.ui.utility.text;
|
||||
using mbts.ui.viewmodels;
|
||||
using System;
|
||||
using System.Windows;
|
||||
using System.Windows.Media;
|
||||
using FontFamily = System.Windows.Media.FontFamily;
|
||||
|
||||
namespace bts.shell.pnl {
|
||||
class PNLCellAppearanceOverrideVM : ViewModelBase {
|
||||
bool
|
||||
_is_background_overridden,
|
||||
_is_font_color_overridden,
|
||||
_is_font_weight_overridden,
|
||||
_is_font_family_overridden,
|
||||
_is_font_size_overridden;
|
||||
class PNLCellAppearanceOverrideVM : ParameterGroupVM {
|
||||
public PNLCellAppearanceOverrideVM(string display_name, string rule = null) : base(display_name) {
|
||||
parameters.AddRange(background, font_color, font_weight);
|
||||
background.can_override = true;
|
||||
font_color.can_override = true;
|
||||
font_weight.can_override = true;
|
||||
}
|
||||
|
||||
FontWeight _font_weight_override;
|
||||
FontFamily _font_family_override;
|
||||
double _font_size_override = double.NaN;
|
||||
|
||||
// background
|
||||
public bool is_background_overridden { get => _is_background_overridden; set => Set(ref _is_background_overridden, value); }
|
||||
public ColorPickerPopupVM background_override { get; } = new ColorPickerPopupVM {
|
||||
bind_opacity_to_alpha = true,
|
||||
opacity = 1
|
||||
};
|
||||
// font color
|
||||
public bool is_font_color_overridden { get => _is_font_color_overridden; set => Set(ref _is_font_color_overridden, value); }
|
||||
public ColorPickerPopupVM font_color_override { get; } = new ColorPickerPopupVM();
|
||||
// font weight
|
||||
public bool is_font_weight_overridden { get => _is_font_weight_overridden; set => Set(ref _is_font_weight_overridden, value); }
|
||||
public FontWeight font_weight_override { get => _font_weight_override; set => Set(ref _font_weight_override, value); }
|
||||
// font family
|
||||
public bool is_font_family_overriden { get => _is_font_family_overridden; set => Set(ref _is_font_family_overridden, value); }
|
||||
public FontFamily font_family_override { get => _font_family_override; set => Set(ref _font_family_override, value); }
|
||||
// font size
|
||||
public bool is_font_size_overriden { get => _is_font_size_overridden; set => Set(ref _is_font_size_overridden, value); }
|
||||
public double font_size_override { get => _font_size_override; set => Set(ref _font_size_override, value); }
|
||||
public ColorOverrideParameterVM background { get; } = new ColorOverrideParameterVM("Background");
|
||||
public ColorOverrideParameterVM font_color { get; } = new ColorOverrideParameterVM("Font Color");
|
||||
public FontWeightOverrideParameterVM font_weight { get; } = new FontWeightOverrideParameterVM("Font Weight");
|
||||
|
||||
public GridColumnSettings apply_to(GridColumnSettings target) =>
|
||||
target.with(
|
||||
font_weight_override: new NullableFontWeightProto(is_font_weight_overridden ? font_weight_override : (FontWeight?)null),
|
||||
font_color_override: new NullableColorProto(is_font_color_overridden ? font_color_override.color : (Color?)null),
|
||||
background_color_override: new NullableColorProto(is_background_overridden ? background_override.color : (Color?)null));
|
||||
font_weight_override: new NullableFontWeightProto(font_weight.effective_is_overridden ? font_weight.override_value : (FontWeight?)null),
|
||||
font_color_override: new NullableColorProto(font_color.effective_is_overridden ? font_color.override_value : (Color?)null),
|
||||
background_color_override: new NullableColorProto(background.effective_is_overridden ? background.override_value : (Color?)null));
|
||||
|
||||
public void load(GridColumnSettings settings) {
|
||||
if (settings != null)
|
||||
load(settings.font_weight_override, settings.font_color_override, settings.background_color_override);
|
||||
public IDisposable connect_default_values(GridSettingsVM grid_defaults) =>
|
||||
Disposable.Create(
|
||||
grid_defaults.subscribe_font().Subscribe(v => {
|
||||
font_weight.family = v.family;
|
||||
font_weight.default_value = v.weight;
|
||||
font_weight.size = v.size;
|
||||
}),
|
||||
grid_defaults.subscribe_font_color().Subscribe(v => {
|
||||
font_color.default_value = v;
|
||||
}),
|
||||
grid_defaults.subscribe_background().Subscribe(v => {
|
||||
background.default_value = v;
|
||||
}));
|
||||
|
||||
public PNLAnimation create(PNLView.Columns col_key) {
|
||||
var rv = new PNLAnimation {
|
||||
column = col_key
|
||||
};
|
||||
if (background.effective_is_overridden)
|
||||
rv.background = background.override_value.rgb_color();
|
||||
if (font_color.effective_is_overridden)
|
||||
rv.foreground = font_color.override_value.rgb_color();
|
||||
if (font_weight.effective_is_overridden)
|
||||
rv.font_weight = font_weight.override_value.ToOpenTypeWeight();
|
||||
return rv;
|
||||
}
|
||||
|
||||
public void load(FontParamsProto font_params) {
|
||||
if (font_params == null)
|
||||
return;
|
||||
public void load(GridColumnSettings value) {
|
||||
// font weight
|
||||
is_font_weight_overridden = font_params.font_weight > 0;
|
||||
if (is_font_weight_overridden)
|
||||
font_weight_override = FontWeight.FromOpenTypeWeight(font_params.font_weight);
|
||||
font_weight.is_overridden = value.font_weight_override.HasValue;
|
||||
if (font_weight.is_overridden)
|
||||
font_weight.override_value = value.font_weight_override.Value;
|
||||
// font color
|
||||
is_font_color_overridden = font_params.font_color > 0;
|
||||
if (is_font_color_overridden)
|
||||
font_color_override.color = font_params.font_color.rgb_color();
|
||||
// font family
|
||||
if (!string.IsNullOrEmpty(font_params.font_family)) {
|
||||
font_family_override = new FontFamily(font_params.font_family);
|
||||
}
|
||||
if (!double.IsNaN(font_params.font_size) && font_params.font_size > 0) {
|
||||
font_size_override = font_params.font_size;
|
||||
} else {
|
||||
font_size_override = double.NaN;
|
||||
}
|
||||
}
|
||||
|
||||
public void load(PNLAnimation animation) {
|
||||
if (animation != null)
|
||||
load(
|
||||
animation.font_weight.HasValue ? FontWeight.FromOpenTypeWeight(animation.font_weight.Value) : (FontWeight?)null,
|
||||
animation.foreground?.argb_color(),
|
||||
animation.background?.argb_color());
|
||||
}
|
||||
|
||||
public void load(FontWeight? font_weight, Color? font_color, Color? background) {
|
||||
// font weight
|
||||
is_font_weight_overridden = font_weight.HasValue;
|
||||
if (is_font_weight_overridden)
|
||||
font_weight_override = font_weight.Value;
|
||||
// font color
|
||||
is_font_color_overridden = font_color.HasValue;
|
||||
if (is_font_color_overridden)
|
||||
font_color_override.color = font_color.Value;
|
||||
font_color.is_overridden = value.font_color_override.HasValue;
|
||||
if (font_color.is_overridden)
|
||||
font_color.override_value = value.font_color_override.Value;
|
||||
// background
|
||||
is_background_overridden = background.HasValue;
|
||||
if (background.HasValue)
|
||||
background_override.color = background.Value;
|
||||
background.is_overridden = value.background_color_override.HasValue;
|
||||
if (background.is_overridden)
|
||||
background.override_value = value.background_color_override.Value;
|
||||
}
|
||||
|
||||
public PNLAnimation to_animation(PNLView.Columns key) =>
|
||||
new PNLAnimation {
|
||||
column = key,
|
||||
foreground = is_font_color_overridden ? font_color_override.color.rgb_color() : (uint?)null,
|
||||
background = is_background_overridden ? background_override.color.rgb_color() : (uint?)null,
|
||||
font_weight = is_font_weight_overridden ? font_weight_override.ToOpenTypeWeight() : (int?)null
|
||||
};
|
||||
|
||||
public PNLCellAppearanceProto to_appearance_proto() =>
|
||||
new PNLCellAppearanceProto {
|
||||
background = is_background_overridden ? background_override.color.rgb_color() : (uint?)null,
|
||||
font_params = to_font_params()
|
||||
};
|
||||
|
||||
public FontParamsProto to_font_params() =>
|
||||
new FontParamsProto {
|
||||
typeface = new TypefaceProto(
|
||||
font_family_override?.name(),
|
||||
is_font_weight_overridden ? font_weight_override.ToOpenTypeWeight() : 0),
|
||||
font_color = is_font_color_overridden ? font_color_override.color.rgb_color() : 0,
|
||||
font_size = font_size_override
|
||||
};
|
||||
public void load(PNLAnimation value) {
|
||||
// font weight
|
||||
font_weight.is_overridden = value.font_weight.HasValue && value.font_weight.Value > 0;
|
||||
if (font_weight.is_overridden)
|
||||
font_weight.override_value = FontWeight.FromOpenTypeWeight(value.font_weight.Value);
|
||||
// font color
|
||||
font_color.is_overridden = value.foreground.HasValue;
|
||||
if (font_color.is_overridden)
|
||||
font_color.override_value = value.foreground.Value.argb_color();
|
||||
// background
|
||||
background.is_overridden = value.background.HasValue;
|
||||
if (background.is_overridden)
|
||||
background.override_value = value.background.Value.argb_color();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,34 +1,51 @@
|
||||
using mbts.ui.controls;
|
||||
using mbts.ui.shell;
|
||||
using System.Windows;
|
||||
using FontFamily = System.Windows.Media.FontFamily;
|
||||
using bts.collections;
|
||||
using mbts.ui.viewmodels;
|
||||
|
||||
namespace bts.shell.pnl {
|
||||
class PNLCellAppearanceVM : ViewModelBase {
|
||||
FontWeight _font_weight;
|
||||
FontFamily _font_family;
|
||||
double _font_size = double.NaN;
|
||||
class PNLHeaderRowCellAppearanceVM : ParameterGroupVM {
|
||||
readonly FontParameterVM _font = new FontParameterVM("Font");
|
||||
readonly ColorParameterVM
|
||||
_background = new ColorParameterVM("Background"),
|
||||
_font_color = new ColorParameterVM("Font Color");
|
||||
readonly BooleanParameterVM
|
||||
_show_totals = new BooleanParameterVM("Show Totals"),
|
||||
_show_selected_totals = new BooleanParameterVM("Show Selected Totals");
|
||||
|
||||
public ColorPickerPopupVM background { get; } = new ColorPickerPopupVM { bind_opacity_to_alpha = true, opacity = 1 };
|
||||
public ColorPickerPopupVM font_color { get; } = new ColorPickerPopupVM();
|
||||
public FontWeight font_weight { get => _font_weight; set => Set(ref _font_weight, value); }
|
||||
public FontFamily font_family { get => _font_family; set => Set(ref _font_family, value); }
|
||||
public double font_size { get => _font_size; set => Set(ref _font_size, value); }
|
||||
public PNLHeaderRowCellAppearanceVM() : base("Header Row Display") {
|
||||
parameters.AddRange(
|
||||
HeaderedParameterGroupVM.create("Fonts/Colors", _font, _font_color, _background),
|
||||
HeaderedParameterGroupVM.create("Display", _show_totals, _show_selected_totals));
|
||||
}
|
||||
|
||||
public PNLHeaderRowDisplay display {
|
||||
get {
|
||||
var rv = PNLHeaderRowDisplay.None;
|
||||
if (_show_totals.value)
|
||||
rv |= PNLHeaderRowDisplay.Total;
|
||||
if (_show_selected_totals.value)
|
||||
rv |= PNLHeaderRowDisplay.SelectedTotal;
|
||||
return rv;
|
||||
}
|
||||
set {
|
||||
_show_totals.value = (display & PNLHeaderRowDisplay.Total) != 0;
|
||||
_show_selected_totals.value = (display & PNLHeaderRowDisplay.SelectedTotal) != 0;
|
||||
}
|
||||
}
|
||||
|
||||
public PNLCellAppearance to_appearance() =>
|
||||
new PNLCellAppearance(font_color.color, background.color, font_weight, font_family, font_size);
|
||||
new PNLCellAppearance(_font_color.value, _background.value, _font.weight, _font.family, _font.size);
|
||||
|
||||
public void load(PNLCellAppearance appearance) {
|
||||
if (appearance.background.HasValue)
|
||||
background.color = appearance.background.Value;
|
||||
_background.value = appearance.background.Value;
|
||||
if (appearance.foreground.HasValue)
|
||||
font_color.color = appearance.foreground.Value;
|
||||
_font_color.value = appearance.foreground.Value;
|
||||
if (appearance.font_family != null)
|
||||
font_family = appearance.font_family;
|
||||
_font.family = appearance.font_family;
|
||||
if (appearance.font_weight.HasValue)
|
||||
font_weight = appearance.font_weight.Value;
|
||||
_font.weight = appearance.font_weight.Value;
|
||||
if (appearance.font_size > 0)
|
||||
font_size = appearance.font_size;
|
||||
_font.size = appearance.font_size;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,133 +1,216 @@
|
||||
using bts.collections;
|
||||
using bts.utils;
|
||||
using mbts;
|
||||
using mbts.spark;
|
||||
using mbts.ui.controls;
|
||||
using mbts.ui.settings;
|
||||
using mbts.ui.shell;
|
||||
using mbts.ui.utilities;
|
||||
using mbts.ui.utility;
|
||||
using mbts.ui.viewmodels;
|
||||
using mbts.utils;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.ComponentModel;
|
||||
using System.Collections.Specialized;
|
||||
using System.Linq;
|
||||
using System.Reactive.Linq;
|
||||
using System.Windows;
|
||||
|
||||
namespace bts.shell.pnl {
|
||||
class PNLColumnVM : ViewModelBase, ISimpleColumnVM {
|
||||
readonly SimpleGridSettingsVM _owner;
|
||||
readonly PNLView.Columns _pnl_id;
|
||||
object _selected_item;
|
||||
string _display_name;
|
||||
bool _is_visible;
|
||||
double _width;
|
||||
HorizontalAlignment _horizontal_alignment;
|
||||
class PNLModularCellAppearanceVM : TabularParameterGroupVM {
|
||||
public static PNLModularCellAppearanceVM create(PNLColumnCellAppearanceVM default_cell_appearance, IReadOnlyObservableCollection<object> rules) {
|
||||
var composite = new ObservableAggregateCollection<object>(new ObservableCollection<object> { default_cell_appearance });
|
||||
composite.add_collection(rules);
|
||||
return new PNLModularCellAppearanceVM(composite) {
|
||||
selected_item = default_cell_appearance
|
||||
};
|
||||
}
|
||||
|
||||
public PNLColumnVM(SimpleGridSettingsVM owner, PNLView.Columns id) {
|
||||
_owner = owner;
|
||||
_pnl_id = id;
|
||||
default_column_vm.PropertyChanged += echo;
|
||||
void echo(object sender, PropertyChangedEventArgs e) {
|
||||
switch (e.PropertyName) {
|
||||
case nameof(is_font_weight_overridden):
|
||||
case nameof(is_font_color_overridden):
|
||||
RaisePropertyChanged(e.PropertyName);
|
||||
PNLModularCellAppearanceVM(ObservableCollection<object> parameters) : base("", parameters) { }
|
||||
}
|
||||
|
||||
class PNLColumnCellAppearanceVM : ParameterGroupVM {
|
||||
PNLCellAppearanceOverrideVM _selected_cell_mode;
|
||||
|
||||
public static Rule create_rule(string rule) =>
|
||||
new Rule(rule);
|
||||
|
||||
public PNLColumnCellAppearanceVM(string display_name) : base(display_name) {
|
||||
parameters.AddRange(default_vm, group_vm, header_vm);
|
||||
_selected_cell_mode = default_vm;
|
||||
}
|
||||
|
||||
public PNLCellAppearanceOverrideVM selected_cell_mode { get => _selected_cell_mode; set => Set(ref _selected_cell_mode, value); }
|
||||
public PNLCellAppearanceOverrideVM default_vm { get; } = new PNLCellAppearanceOverrideVM("Row") { is_expanded = true };
|
||||
public PNLCellAppearanceOverrideVM group_vm { get; } = new PNLCellAppearanceOverrideVM("Group Row");
|
||||
public PNLCellAppearanceOverrideVM header_vm { get; } = new PNLCellAppearanceOverrideVM("Header Row");
|
||||
|
||||
IEnumerable<PNLCellAppearanceOverrideVM> _cell_modes {
|
||||
get {
|
||||
yield return default_vm;
|
||||
yield return group_vm;
|
||||
yield return header_vm;
|
||||
}
|
||||
}
|
||||
|
||||
public IDisposable connect_default_values(GridSettingsVM grid_defaults) =>
|
||||
new System.Reactive.Disposables.CompositeDisposable(
|
||||
_cell_modes.Select(c => c.connect_default_values(grid_defaults)));
|
||||
|
||||
public class Rule : PNLColumnCellAppearanceVM {
|
||||
public Rule(string rule_text) : base($"When {DisplayNameHelper.expression_snake_to_title_case(rule_text)}") =>
|
||||
this.rule_text = rule_text;
|
||||
|
||||
public string rule_text { get; }
|
||||
}
|
||||
}
|
||||
|
||||
class PNLColumnSettingsVM : GridColumnSettingsVM {
|
||||
readonly ObservableAggregateCollection<object> _cell_appearance_groups = new ObservableAggregateCollection<object>();
|
||||
readonly _AutoRuleTabsWrapperVM _fonts_and_colors;
|
||||
readonly PNLView.Columns _key;
|
||||
|
||||
public PNLColumnSettingsVM(PNLView.Columns key) : base(null) {
|
||||
_key = key;
|
||||
_fonts_and_colors = new _AutoRuleTabsWrapperVM("Fonts/Colors", key);
|
||||
column_layout = new HeaderedParameterGroupVM("Column Layout", new ObservableCollection<object> {
|
||||
header, width, horizontal_alignment
|
||||
});
|
||||
apply_flags(GridColumnSettingsFeatureFlags.None);
|
||||
}
|
||||
|
||||
public HeaderedParameterGroupVM column_layout { get; }
|
||||
public HeaderedParameterGroupVM fonts_and_colors => _fonts_and_colors;
|
||||
public PNLColumnCellAppearanceVM default_cell_appearance => _fonts_and_colors.cell_appearance;
|
||||
|
||||
IEnumerable<PNLCellAppearanceOverrideVM> _all_cell_modes =>
|
||||
_fonts_and_colors.parameters
|
||||
.OfType<PNLColumnCellAppearanceVM>()
|
||||
.SelectMany(c => c.parameters.OfType<PNLCellAppearanceOverrideVM>());
|
||||
|
||||
public void load_from(GridColumnSettings value, IReadOnlyDictionary<string, PNLAnimation> triggers, GridColumnSettings group, IReadOnlyDictionary<string, PNLAnimation> group_triggers, IReadOnlyDictionary<string, PNLAnimation> header_triggers, GridColumnSettings header) {
|
||||
load_from(value);
|
||||
_fonts_and_colors.load_from(value, triggers, group, group_triggers, header_triggers, header);
|
||||
}
|
||||
|
||||
public IEnumerable<(string expression, PNLAnimation row, PNLAnimation group, PNLAnimation header)> get_triggers() =>
|
||||
_fonts_and_colors.create();
|
||||
|
||||
public IDisposable connect_default_values(GridSettingsVM grid_defaults) =>
|
||||
_fonts_and_colors.connect_default_values(grid_defaults);
|
||||
|
||||
public override string ToString() => "Default";
|
||||
|
||||
public override void sync_params() =>
|
||||
parameters.SetItems(column_layout, fonts_and_colors);
|
||||
|
||||
/// <summary>
|
||||
/// This class stores a <see cref="PNLColumnCellAppearanceVM"/> instance in a collection of parameters, and automatically replaces it with a <see cref="PNLModularCellAppearanceVM"/> when rules are loaded.
|
||||
/// Conversely, it replaces the <see cref="PNLModularCellAppearanceVM"/> with the original instance when rules are unloaded.
|
||||
/// The impetus: A special 'TabControl' based DataTemplate is defined in PNLSettingsView.xaml as the default for content of type <see cref="PNLModularCellAppearanceVM"/>.
|
||||
/// </summary>
|
||||
class _AutoRuleTabsWrapperVM : HeaderedParameterGroupVM {
|
||||
public readonly PNLColumnCellAppearanceVM cell_appearance;
|
||||
|
||||
readonly ObservableAggregateCollection<object> _composite_parameters;
|
||||
readonly ObservableCollection<PNLColumnCellAppearanceVM.Rule> _rules = new ObservableCollection<PNLColumnCellAppearanceVM.Rule>();
|
||||
readonly IReadOnlyObservableCollection<object> _wrapped_rules;
|
||||
readonly DeferrableActionWrapper _sync_params;
|
||||
PNLModularCellAppearanceVM _cell_appearance_rules;
|
||||
PNLView.Columns _id;
|
||||
|
||||
public _AutoRuleTabsWrapperVM(string display_name, PNLView.Columns id)
|
||||
: this(display_name, id, new ObservableAggregateCollection<object>()) { }
|
||||
|
||||
_AutoRuleTabsWrapperVM(string display_name, PNLView.Columns id, ObservableAggregateCollection<object> parameters) : base(display_name, parameters) {
|
||||
_id = id;
|
||||
_composite_parameters = parameters;
|
||||
cell_appearance = new PNLColumnCellAppearanceVM("Default");
|
||||
_wrapped_rules = _rules.CastObservable<PNLColumnCellAppearanceVM.Rule, object>();
|
||||
//_wrapped_rules = _rules.SelectObservable<PNLColumnCellAppearanceVM, object>(_ => _, obj => (PNLColumnCellAppearanceVM)obj);
|
||||
// If there are no rules present, the view should display a flat list of font/colors parameters.
|
||||
// If there are rules present, the view should use TabItems to represent each rule its associated font/color parameters,
|
||||
// as well as an additional Tab for the 'defaults.'
|
||||
// It is the responsibility of this class, as the viewmodel, to organize the parameters appropriately.
|
||||
// Listen to rules.CollectionChanged events, and toggle between the 'flat list of default parameters' and the TabItem groups when rules.Count > 0 changes.
|
||||
_composite_parameters.add_collection(cell_appearance.parameters);
|
||||
_composite_parameters.add_collection(_wrapped_rules);
|
||||
bool rules_present = false;
|
||||
_sync_params = new DeferrableActionWrapper(sync_params);
|
||||
_rules.CollectionChanged += (s, e) => _sync_params.execute();
|
||||
void sync_params() {
|
||||
if (rules_present != _rules.Count > 0) {
|
||||
rules_present = !rules_present;
|
||||
_composite_parameters.clear_collections();
|
||||
if (rules_present) {
|
||||
if (_cell_appearance_rules == null) {
|
||||
_cell_appearance_rules = PNLModularCellAppearanceVM.create(cell_appearance, _wrapped_rules);
|
||||
}
|
||||
_composite_parameters.insert_collection(0, new ObservableCollection<object> { _cell_appearance_rules });
|
||||
} else {
|
||||
_composite_parameters.insert_collection(0, new ObservableCollection<object> { cell_appearance });
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public IEnumerable<(string rule, PNLAnimation row, PNLAnimation group, PNLAnimation header)> create() =>
|
||||
_rules.Select(r => (r.rule_text, r.default_vm.create(_id), r.group_vm.create(_id), r.header_vm.create(_id)));
|
||||
|
||||
public void load_from(GridColumnSettings value, IReadOnlyDictionary<string, PNLAnimation> triggers, GridColumnSettings group, IReadOnlyDictionary<string, PNLAnimation> group_triggers, IReadOnlyDictionary<string, PNLAnimation> header_triggers, GridColumnSettings header) {
|
||||
using (_sync_params.defer_execution()) {
|
||||
_rules.Clear();
|
||||
cell_appearance.default_vm.load(value);
|
||||
cell_appearance.group_vm.load(group);
|
||||
cell_appearance.header_vm.load(header);
|
||||
foreach (var kv in triggers) {
|
||||
var rule_vm = PNLColumnCellAppearanceVM.create_rule(kv.Key);
|
||||
rule_vm.default_vm.load(kv.Value);
|
||||
if (group_triggers.TryGetValue(kv.Key, out var g)) {
|
||||
rule_vm.group_vm.load(g);
|
||||
}
|
||||
if (header_triggers.TryGetValue(kv.Key, out var h))
|
||||
rule_vm.header_vm.load(h);
|
||||
_rules.Add(rule_vm);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
GridSettingsVM _grid_defaults;
|
||||
|
||||
public IDisposable connect_default_values(GridSettingsVM grid_defaults) {
|
||||
Log.assert(_grid_defaults == null && grid_defaults != null);
|
||||
if (_rules.Count > 0) { }
|
||||
_grid_defaults = grid_defaults;
|
||||
_rules.CollectionChanged += _wtf;
|
||||
_wtf(_rules, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
|
||||
return Disposable.Create(
|
||||
cell_appearance.connect_default_values(grid_defaults),
|
||||
ObservableAutoWrapper.connect_item_hooks(_rules, r => r.connect_default_values(grid_defaults)),
|
||||
ObservableAutoWrapper.connect_item_hooks(parameters,
|
||||
obj => {
|
||||
if (obj is PNLColumnCellAppearanceVM cell_app) {
|
||||
return cell_app.connect_default_values(grid_defaults);
|
||||
} else if (obj is PNLCellAppearanceOverrideVM cell_app_over) {
|
||||
return cell_app_over.connect_default_values(grid_defaults);
|
||||
}
|
||||
return null;
|
||||
}));
|
||||
}
|
||||
|
||||
private void _wtf(object sender, NotifyCollectionChangedEventArgs e) {
|
||||
if (_rules.Count > 0) {
|
||||
}
|
||||
switch (e.Action) {
|
||||
case NotifyCollectionChangedAction.Reset:
|
||||
break;
|
||||
case NotifyCollectionChangedAction.Add:
|
||||
break;
|
||||
case NotifyCollectionChangedAction.Remove:
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public PNLCellAppearanceOverrideVM default_column_vm { get; } = new PNLCellAppearanceOverrideVM();
|
||||
public PNLCellAppearanceOverrideVM group_column_vm { get; } = new PNLCellAppearanceOverrideVM();
|
||||
public PNLCellAppearanceOverrideVM header_column_vm { get; } = new PNLCellAppearanceOverrideVM();
|
||||
//
|
||||
public ObservableCollection<PNLRuleColumnVM> rule_columns { get; } = new ObservableCollection<PNLRuleColumnVM>();
|
||||
|
||||
public object selected_item {
|
||||
get => _selected_item;
|
||||
set {
|
||||
if (value != null)
|
||||
Set(ref _selected_item, value);
|
||||
}
|
||||
}
|
||||
|
||||
public string id { get => _pnl_id.ToString(); set => Log.assert(value == _pnl_id.ToString()); }
|
||||
public string display_name { get => _display_name; set => Set(ref _display_name, value); }
|
||||
public double width { get => _width; set => Set(ref _width, value); }
|
||||
public bool is_visible { get => _is_visible; set => Set(ref _is_visible, value); }
|
||||
public bool is_font_color_overridden {
|
||||
get => default_column_vm.is_font_color_overridden;
|
||||
set => default_column_vm.is_font_color_overridden = value;
|
||||
}
|
||||
public HorizontalAlignment horizontal_alignment { get => _horizontal_alignment; set => Set(ref _horizontal_alignment, value); }
|
||||
|
||||
public ColorPickerPopupVM font_color_override => default_column_vm.font_color_override;
|
||||
|
||||
public bool is_font_weight_overridden { get => default_column_vm.is_font_weight_overridden; set => default_column_vm.is_font_weight_overridden = value; }
|
||||
public FontWeight font_weight_override { get => default_column_vm.font_weight_override; set => default_column_vm.font_weight_override = value; }
|
||||
|
||||
public void apply_flags(GridColumnSettingsFeatureFlags flags) { }
|
||||
|
||||
public GridColumnSettings create() {
|
||||
var result = new GridColumnSettings(id: id, display_name: display_name, is_visible: is_visible, horizontal_alignment: horizontal_alignment, width: width);
|
||||
result = default_column_vm.apply_to(result);
|
||||
return result;
|
||||
}
|
||||
|
||||
public void load_from(GridColumnSettings value) {
|
||||
_assert_id(value.id);
|
||||
default_column_vm.load(value);
|
||||
display_name = value.display_name;
|
||||
width = value.width;
|
||||
is_visible = value.is_visible;
|
||||
horizontal_alignment = value.horizontal_alignment;
|
||||
}
|
||||
|
||||
public void load_from(GridColumnSettings value, IReadOnlyDictionary<string, PNLAnimation> triggers, GridColumnSettings group, IReadOnlyDictionary<string, PNLAnimation> group_triggers, IReadOnlyDictionary<string, PNLAnimation> header_triggers, GridColumnSettings header) {
|
||||
_assert_id(value.id);
|
||||
load_from(value);
|
||||
if (group != null)
|
||||
group_column_vm.load(group);
|
||||
if (header != null)
|
||||
header_column_vm.load(header);
|
||||
rule_columns.SetItems(triggers.Select(kv => {
|
||||
var rule_col = new PNLRuleColumnVM(kv.Key);
|
||||
rule_col.load_from(kv.Value, group_triggers.GetValueOrDefault(kv.Key), header_triggers.GetValueOrDefault(kv.Key));
|
||||
return rule_col;
|
||||
}));
|
||||
}
|
||||
|
||||
public IEnumerable<(string expression, PNLAnimation row, PNLAnimation group, PNLAnimation header)> get_triggers() {
|
||||
foreach (var col in rule_columns)
|
||||
yield return (col.rule_text, col.row_params.to_animation(_pnl_id), col.group_params.to_animation(_pnl_id), col.header_params.to_animation(_pnl_id));
|
||||
}
|
||||
|
||||
void _assert_id(string id) =>
|
||||
Log.assert(id == _pnl_id.ToString(), $"PNLColumn id string '{id}' does not match the expected {typeof(PNLView.Columns)} key '{_pnl_id}.'");
|
||||
|
||||
public override string ToString() => "Default";
|
||||
}
|
||||
|
||||
class PNLRuleColumnVM : ViewModelBase, IEquatable<PNLRuleColumnVM> {
|
||||
public PNLRuleColumnVM(string rule_text) =>
|
||||
this.rule_text = rule_text;
|
||||
|
||||
public string rule_text { get; }
|
||||
|
||||
public PNLCellAppearanceOverrideVM row_params { get; } = new PNLCellAppearanceOverrideVM();
|
||||
public PNLCellAppearanceOverrideVM group_params { get; } = new PNLCellAppearanceOverrideVM();
|
||||
public PNLCellAppearanceOverrideVM header_params { get; } = new PNLCellAppearanceOverrideVM();
|
||||
|
||||
public void load_from(PNLAnimation row, PNLAnimation group, PNLAnimation header) {
|
||||
row_params.load(row);
|
||||
group_params.load(group);
|
||||
header_params.load(header);
|
||||
}
|
||||
|
||||
public bool Equals(PNLRuleColumnVM other) =>
|
||||
other != null && rule_text == other.rule_text;
|
||||
|
||||
public override bool Equals(object obj) => Equals(obj as PNLRuleColumnVM);
|
||||
public override int GetHashCode() => rule_text.GetHashCode();
|
||||
public override string ToString() => rule_text;
|
||||
}
|
||||
}
|
||||
@@ -1,58 +1,60 @@
|
||||
using bts.collections;
|
||||
using mbts.spark;
|
||||
using mbts.ui;
|
||||
using mbts.ui.controls;
|
||||
using mbts.ui.shell;
|
||||
using bts.utils;
|
||||
using mbts.ui.utility;
|
||||
using mbts.ui.viewmodels;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Windows;
|
||||
using System.Windows.Input;
|
||||
using System.Linq;
|
||||
using System.Windows.Media;
|
||||
using FontFamily = System.Windows.Media.FontFamily;
|
||||
|
||||
namespace bts.shell.pnl {
|
||||
class PNLGroupRowAppearanceVM : ViewModelBase, IValueFactory<(PNLCellAppearance appearance, IReadOnlyList<Color> background_colors)> {
|
||||
FontWeight _font_weight;
|
||||
FontFamily _font_family;
|
||||
double _font_size = double.NaN;
|
||||
class PNLGroupRowCellAppearanceVM : ParameterGroupVM {
|
||||
readonly PNLAlternatingRowBackgroundVM _alternating_background_vm = new PNLAlternatingRowBackgroundVM();
|
||||
readonly ColorParameterVM _font_color = new ColorParameterVM("Font Color");
|
||||
readonly FontParameterVM _font = new FontParameterVM("Font");
|
||||
|
||||
public PNLGroupRowAppearanceVM() {
|
||||
add_background_cmd = new DelegateCommand(_add_background);
|
||||
// 4 elements is enough to alternate through any reasonable number of groups,
|
||||
// and clicking to add new backgrounds seems awkward
|
||||
for (int i = 0; i < 4; i++)
|
||||
_add_background();
|
||||
public PNLGroupRowCellAppearanceVM() : base("Group") {
|
||||
parameters.AddRange(
|
||||
new HeaderedParameterGroupVM("Fonts/Colors", new ObservableCollection<object> { _font, _font_color }),
|
||||
_alternating_background_vm);
|
||||
}
|
||||
|
||||
public ObservableCollection<NamedColorPickerPopupVM> background_colors { get; } = new ObservableCollection<NamedColorPickerPopupVM>();
|
||||
public ColorPickerPopupVM font_color { get; } = new ColorPickerPopupVM();
|
||||
public FontWeight font_weight { get => _font_weight; set => Set(ref _font_weight, value); }
|
||||
public FontFamily font_family { get => _font_family; set => Set(ref _font_family, value); }
|
||||
public double font_size { get => _font_size; set => Set(ref _font_size, value); }
|
||||
public ICommand add_background_cmd { get; }
|
||||
ObservableCollection<ColorParameterVM> _alternating_backgrounds => _alternating_background_vm.items;
|
||||
|
||||
public (PNLCellAppearance appearance, IReadOnlyList<Color> background_colors) create() =>
|
||||
(new PNLCellAppearance(font_color.color, Colors.Transparent, font_weight, font_family, font_size),
|
||||
background_colors.WrapSelectorRO(cp => cp.color));
|
||||
public (PNLCellAppearance appearance, IReadOnlyList<Color> alternating_backgrounds) create() =>
|
||||
(new PNLCellAppearance(_font_color.value, Colors.Transparent, _font.weight, _font.family, _font.size),
|
||||
_alternating_backgrounds.Cast<ColorParameterVM>().Select(vm => vm.value).ToList());
|
||||
|
||||
public void load_from((PNLCellAppearance appearance, IReadOnlyList<Color> background_colors) value) {
|
||||
var (app, bg_colors) = value;
|
||||
// background colors
|
||||
while (background_colors.Count < bg_colors.Count)
|
||||
_add_background();
|
||||
for (int i = 0; i < bg_colors.Count; i++)
|
||||
background_colors[i].color = bg_colors[i];
|
||||
//
|
||||
if (app.foreground.HasValue) font_color.color = app.foreground.Value;
|
||||
if (app.font_family != null) font_family = app.font_family;
|
||||
if (app.font_weight.HasValue) font_weight = app.font_weight.Value;
|
||||
if (app.font_size > 0) font_size = app.font_size;
|
||||
public void load((PNLCellAppearance appearance, IReadOnlyList<Color> alternating_backgrounds) value) {
|
||||
var (appearance, alternating_backgrounds) = value;
|
||||
for (int i = 0; i < alternating_backgrounds.Count; i++) {
|
||||
if (_alternating_backgrounds.Count > i) {
|
||||
var vm = _alternating_backgrounds[i];
|
||||
vm.value = alternating_backgrounds[i];
|
||||
}
|
||||
}
|
||||
while (alternating_backgrounds.Count > _alternating_backgrounds.Count)
|
||||
_alternating_backgrounds.Add(new ColorParameterVM($"Background {_alternating_backgrounds.Count}") {
|
||||
value = alternating_backgrounds[_alternating_backgrounds.Count]
|
||||
});
|
||||
while (_alternating_backgrounds.Count > alternating_backgrounds.Count)
|
||||
_alternating_backgrounds.RemoveAt(alternating_backgrounds.Count);
|
||||
CollectionHelper.add_or_update_existing(_alternating_backgrounds, alternating_backgrounds, c => new ColorParameterVM("") { value = c }, (vm, c) => ((ColorParameterVM)vm).value = c);
|
||||
if (appearance.foreground.HasValue)
|
||||
_font_color.value = appearance.foreground.Value;
|
||||
if (appearance.font_family != null)
|
||||
_font.family = appearance.font_family;
|
||||
if (appearance.font_weight.HasValue)
|
||||
_font.weight = appearance.font_weight.Value;
|
||||
if (appearance.font_size > 0)
|
||||
_font.size = appearance.font_size;
|
||||
}
|
||||
}
|
||||
|
||||
void _add_background() =>
|
||||
background_colors.Add(new NamedColorPickerPopupVM {
|
||||
bind_opacity_to_alpha = true,
|
||||
name = $"Background {background_colors.Count + 1}"
|
||||
});
|
||||
class PNLAlternatingRowBackgroundVM : HeaderedParameterGroupVM {
|
||||
public PNLAlternatingRowBackgroundVM() : base("Alternating Backgrounds") =>
|
||||
ObservableAutoWrapper.connect(items, parameters, c => c);
|
||||
|
||||
public ObservableCollection<ColorParameterVM> items { get; } = new ObservableCollection<ColorParameterVM>();
|
||||
}
|
||||
}
|
||||
@@ -1,220 +1,145 @@
|
||||
using bts.collections;
|
||||
using bts.utils;
|
||||
using mbts;
|
||||
using mbts.collections;
|
||||
using mbts.expression;
|
||||
using mbts.spark;
|
||||
using mbts.ui;
|
||||
using mbts.ui.settings;
|
||||
using mbts.ui.shell;
|
||||
using mbts.ui.spark;
|
||||
using mbts.ui.utility;
|
||||
using mbts.utils;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Collections.Specialized;
|
||||
using System.Dynamic;
|
||||
using System.Linq;
|
||||
using ColumnKeys = bts.shell.pnl.PNLView.Columns;
|
||||
|
||||
namespace bts.shell.pnl {
|
||||
class PNLSettingsVM : DisposableViewModelBase, IWindowSettingsVM<PNLSettings>, IValueFactory<PNLSettings>, IObservable<PNLSettings>, ITemplateSelectorKey {
|
||||
readonly IReadOnlyDictionary<ColumnKeys, AnchorItemVM> _anchor_cache;
|
||||
readonly DeferrableActionWrapper _sync_anchors;
|
||||
|
||||
PNLCalculationType _calculation_type;
|
||||
PNLHeaderRowDisplay _header_row_display;
|
||||
PNLSettings _last_loaded;
|
||||
|
||||
public PNLSettingsVM(PNLSettings settings) {
|
||||
// init anchor cache, deferrable resync
|
||||
_anchor_cache = PNLSettings.anchorable_columns.ToDictionary(k => k, k => new AnchorItemVM(this, k));
|
||||
_sync_anchors = new DeferrableActionWrapper(() => {
|
||||
for (int i = 0; i < anchor_items.Count; i++) {
|
||||
anchor_items[i].anchor_index = i + 1;
|
||||
anchor_items[i].refresh();
|
||||
}
|
||||
});
|
||||
core_settings_vm = new SimpleGridSettingsVM(column_factory: col_settings => {
|
||||
var col_vm = new PNLColumnVM(core_settings_vm, EnumHelper.parse<ColumnKeys>(col_settings.id));
|
||||
col_vm.load_from(col_settings);
|
||||
return col_vm;
|
||||
});
|
||||
core_settings_vm.selection_highlight.bind_opacity_to_alpha = true;
|
||||
_dispose = Disposable.Create(
|
||||
anchor_items.Subscribe((s, e) => _sync_anchors.execute()));
|
||||
load(settings);
|
||||
}
|
||||
|
||||
public IEnumerable<AnchorItemVM> anchor_item_headers => _anchor_cache.Values;
|
||||
public ObservableCollection<AnchorItemVM> anchor_items { get; } = new ObservableCollection<AnchorItemVM>();
|
||||
public SimpleGridSettingsVM core_settings_vm { get; }
|
||||
public PNLGroupRowAppearanceVM group_row_defaults { get; } = new PNLGroupRowAppearanceVM();
|
||||
public PNLCellAppearanceVM header_row_defaults { get; } = new PNLCellAppearanceVM();
|
||||
public PNLCalculationType calculation_type { get => _calculation_type; set => Set(ref _calculation_type, value); }
|
||||
public IEnumerable<PNLCalculationType> calculation_types => EnumHelper.get_values_except(PNLCalculationType.None, PNLCalculationType.Same, PNLCalculationType.Opposite);
|
||||
public PNLHeaderRowDisplay header_row_display { get => _header_row_display; set => Set(ref _header_row_display, value); }
|
||||
public IEnumerable<PNLHeaderRowDisplay> header_row_display_options => PNLSettings.header_row_display_options;
|
||||
|
||||
string ITemplateSelectorKey.key => GridSettingsTemplateKeys.PNL;
|
||||
|
||||
public PNLSettings get_result() {
|
||||
var s = _last_loaded == null ? new PNLSettings() : SparkUtils.clone_proto(_last_loaded);
|
||||
s.calculation_type = _calculation_type;
|
||||
s.grid = core_settings_vm.create();
|
||||
using bts.collections;
|
||||
using mbts;
|
||||
using mbts.expression;
|
||||
using mbts.spark;
|
||||
using mbts.ui;
|
||||
using mbts.ui.settings;
|
||||
using mbts.ui.shell;
|
||||
using mbts.ui.spark;
|
||||
using mbts.ui.utility;
|
||||
using mbts.utils;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using ColumnKeys = bts.shell.pnl.PNLView.Columns;
|
||||
|
||||
namespace bts.shell.pnl {
|
||||
class PNLSettingsVM : DisposableViewModelBase, IWindowSettingsVM<PNLSettings>, IValueFactory<PNLSettings>, IObservable<PNLSettings>, ITemplateSelectorKey {
|
||||
PNLCalculationType _calculation_type;
|
||||
PNLSettings _last_loaded;
|
||||
|
||||
public PNLSettingsVM(PNLSettings settings) {
|
||||
core_settings_vm = new GridSettingsVM();
|
||||
columns = core_settings_vm.columns.CastObservable<GridColumnSettingsVM, PNLColumnSettingsVM>();
|
||||
load(settings);
|
||||
_dispose = ObservableAutoWrapper.connect_item_hooks(columns, col => col.connect_default_values(core_settings_vm));
|
||||
}
|
||||
|
||||
public PNLAnchorHierarchyVM anchor_hierarcy { get; } = new PNLAnchorHierarchyVM();
|
||||
public GridSettingsVM core_settings_vm { get; }
|
||||
public PNLGroupRowCellAppearanceVM group_row_defaults { get; } = new PNLGroupRowCellAppearanceVM();
|
||||
public PNLHeaderRowCellAppearanceVM header_row_defaults { get; } = new PNLHeaderRowCellAppearanceVM();
|
||||
public PNLCalculationType calculation_type { get => _calculation_type; set => Set(ref _calculation_type, value); }
|
||||
public IEnumerable<PNLCalculationType> calculation_types => EnumHelper.get_values_except(PNLCalculationType.None, PNLCalculationType.Same, PNLCalculationType.Opposite);
|
||||
public IReadOnlyObservableCollection<PNLColumnSettingsVM> columns { get; }
|
||||
string ITemplateSelectorKey.key => GridSettingsTemplateKeys.PNL;
|
||||
|
||||
public PNLSettings get_result() {
|
||||
var s = _last_loaded == null ? new PNLSettings() : SparkUtils.clone_proto(_last_loaded);
|
||||
s.calculation_type = _calculation_type;
|
||||
s.grid = core_settings_vm.get_result();
|
||||
// move anchor cols to the left, order wrt grouping hierarchy
|
||||
var new_cols = new Lazy<List<GridColumnSettings>>(() => s.grid.columns.ToList());
|
||||
for (int i = 0; i < s.grid.columns.Count; i++) {
|
||||
var key = s.grid.columns[i].strong_id<ColumnKeys>();
|
||||
if (_anchor_cache.TryGetValue(key, out var a) && a.anchor_index.HasValue) {
|
||||
// anchor index is 1 based, subtract 1 to get 0 based
|
||||
int anchor_index = a.anchor_index.Value - 1;
|
||||
if (anchor_index != i && anchor_index >= 0 && anchor_index < s.grid.columns.Count) {
|
||||
new_cols.Value.RemoveAt(i);
|
||||
new_cols.Value.Insert(anchor_index, s.grid.columns[i]);
|
||||
}
|
||||
int anchor_index = anchor_hierarcy.value.indexOf(key);
|
||||
if (anchor_index != i && anchor_index >= 0) {
|
||||
new_cols.Value.RemoveAt(i);
|
||||
new_cols.Value.Insert(anchor_index, s.grid.columns[i]);
|
||||
}
|
||||
}
|
||||
// rule-based animations
|
||||
var trigger_anims = core_settings_vm.columns
|
||||
.OfType<PNLColumnVM>()
|
||||
.SelectMany(c => c.get_triggers())
|
||||
.GroupBy(tup => tup.expression)
|
||||
.Where(g => CoreExpressionEvaluator<PNLRowData>.validate(g.Key))
|
||||
.Select(g => (
|
||||
expression: g.Key,
|
||||
animations: g.ToArray()))
|
||||
.ToList();
|
||||
|
||||
var normalized_anims = trigger_anims
|
||||
.Where(tup => CoreExpressionEvaluator<PNLNormalizedData>.validate(tup.expression))
|
||||
.ToList();
|
||||
|
||||
s = s.with(anchors: anchor_items.Select(vm => vm.key),
|
||||
grid: new_cols.IsValueCreated ? s.grid.with(columns: new_cols.Value) : null,
|
||||
group_columns: core_settings_vm.columns.OfType<PNLColumnVM>().Select(c => {
|
||||
var col_settings = c.create();
|
||||
col_settings = c.group_column_vm.apply_to(col_settings);
|
||||
return col_settings;
|
||||
}),
|
||||
header_columns: core_settings_vm.columns.OfType<PNLColumnVM>().Select(c => {
|
||||
var col_settings = c.create();
|
||||
col_settings = c.header_column_vm.apply_to(col_settings);
|
||||
return col_settings;
|
||||
}),
|
||||
animations: trigger_anims.Select(
|
||||
ta => new PNLAnimationTriggerData {
|
||||
expression = ta.expression,
|
||||
animations = Array.ConvertAll(ta.animations, t => t.row)
|
||||
}),
|
||||
group_header_animations: normalized_anims
|
||||
.Select(
|
||||
ta => new PNLAnimationTriggerData {
|
||||
expression = ta.expression,
|
||||
animations = Array.ConvertAll(ta.animations, t => t.group)
|
||||
}),
|
||||
header_row_animations: normalized_anims.Select(
|
||||
ta => new PNLAnimationTriggerData {
|
||||
expression = ta.expression,
|
||||
animations = Array.ConvertAll(ta.animations, t => t.header)
|
||||
}));
|
||||
s.header_row_display = header_row_display;
|
||||
s.header_cell_appearance = PNLCellAppearanceProto.from_appearance(header_row_defaults.to_appearance());
|
||||
// group
|
||||
var (group_cell_appearance, group_row_background_colors) = group_row_defaults.create();
|
||||
s.group_cell_appearance = PNLCellAppearanceProto.from_appearance(group_cell_appearance);
|
||||
s.group_background_colors.SetItems(group_row_background_colors.Select(c => c.rgb_color()));
|
||||
//
|
||||
return s;
|
||||
}
|
||||
|
||||
public void load(PNLSettings value) {
|
||||
_last_loaded = value;
|
||||
header_row_defaults.load(value.header_cell_appearance.to_appearance());
|
||||
group_row_defaults.load_from((value.group_cell_appearance.to_appearance(), value.group_background_colors.WrapSelectorRO(c => c.argb_color())));
|
||||
var column_map = _map_column_data(value.grid.columns, value.animations);
|
||||
var group_column_map = _map_column_data(value.group_columns, value.group_header_animations);
|
||||
var header_column_map = _map_column_data(value.header_columns, value.header_row_animations);
|
||||
|
||||
core_settings_vm.load_from(value.grid);
|
||||
|
||||
for (int i = 0; i < core_settings_vm.columns.Count; i++) {
|
||||
var col_vm = core_settings_vm.columns[i] as PNLColumnVM ?? throw new InvalidOperationException($"In {nameof(PNLSettingsVM)}, all columns should be of type {typeof(PNLColumnVM)}.");
|
||||
var key = EnumHelper.parse<ColumnKeys>(col_vm.id);
|
||||
var (col_settings, trigger_animations) = column_map[key];
|
||||
var group_data = group_column_map.GetNullable(key);
|
||||
var header_data = header_column_map.GetNullable(key);
|
||||
col_vm.load_from(col_settings, trigger_animations, group_data?.settings, group_data?.triggers, header_data?.triggers, header_data?.settings);
|
||||
}
|
||||
using (_sync_anchors.defer_execution())
|
||||
anchor_items.WrapSelector(a => a.key, k => _anchor_cache[k]).sync_with(value.anchors.Intersect(PNLSettings.anchorable_columns));
|
||||
header_row_display = value.header_row_display;
|
||||
calculation_type = value.calculation_type;
|
||||
}
|
||||
|
||||
PNLSettings IValueFactory<PNLSettings>.create() => get_result();
|
||||
void IValueFactory<PNLSettings>.load_from(PNLSettings value) => load(value);
|
||||
IDisposable IObservable<PNLSettings>.Subscribe(IObserver<PNLSettings> observer) => throw new NotImplementedException();
|
||||
|
||||
static Dictionary<ColumnKeys, (GridColumnSettings settings, Dictionary<string, PNLAnimation> triggers)> _map_column_data(IEnumerable<GridColumnSettings> columns, IEnumerable<PNLAnimationTriggerData> triggers) {
|
||||
var map = columns.ToDictionary(
|
||||
c => c.strong_id<ColumnKeys>(),
|
||||
c => (settings: c, triggers: new Dictionary<string, PNLAnimation>()));
|
||||
foreach (var trig in triggers) {
|
||||
foreach (var anim in trig.animations) {
|
||||
if (map.TryGetValue(anim.column, out var col_data))
|
||||
col_data.triggers[trig.expression] = anim;
|
||||
}
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
void _append_or_remove_anchor(ColumnKeys key, bool include) {
|
||||
if (!_anchor_cache.TryGetValue(key, out var vm))
|
||||
throw new KeyNotFoundException($"Column key '{key}' is not anchorable (expected one of: {string.Join(", ", PNLSettings.anchorable_columns)}).");
|
||||
if (vm.include == include)
|
||||
return;
|
||||
if (include) {
|
||||
if (anchor_items.Any(a => a.key == key))
|
||||
throw new InvalidOperationException($"{nameof(anchor_items)} already contains an element with key '{key}.'");
|
||||
anchor_items.Add(vm);
|
||||
vm.anchor_index = anchor_items.Count;
|
||||
} else {
|
||||
if (!anchor_items.Remove(vm))
|
||||
throw new InvalidOperationException($"{nameof(anchor_items)} does not contain an element with key '{key}.'");
|
||||
vm.anchor_index = null;
|
||||
}
|
||||
_sync_anchors.execute();
|
||||
}
|
||||
|
||||
public class AnchorItemVM : ViewModelBase, IEquatable<AnchorItemVM> {
|
||||
readonly PNLSettingsVM _owner;
|
||||
int? _anchor_index;
|
||||
|
||||
public AnchorItemVM(PNLSettingsVM owner, ColumnKeys key) {
|
||||
_owner = owner;
|
||||
this.key = key;
|
||||
}
|
||||
|
||||
public ColumnKeys key { get; }
|
||||
public bool include { get => anchor_index.HasValue; set => _owner._append_or_remove_anchor(key, value); }
|
||||
public int? anchor_index { get => _anchor_index; set => Set(ref _anchor_index, value); }
|
||||
|
||||
public void refresh() =>
|
||||
RaisePropertyChanged(nameof(include), nameof(anchor_index));
|
||||
|
||||
public bool Equals(AnchorItemVM other) =>
|
||||
other != null && key == other.key;
|
||||
|
||||
public override bool Equals(object obj) =>
|
||||
Equals(obj as AnchorItemVM);
|
||||
|
||||
public override int GetHashCode() =>
|
||||
key.GetHashCode();
|
||||
|
||||
public override string ToString() =>
|
||||
EnumHelper.get_display_name(key);
|
||||
}
|
||||
}
|
||||
}
|
||||
// rule-based animations
|
||||
var trigger_anims = columns
|
||||
.SelectMany(c => c.get_triggers())
|
||||
.GroupBy(tup => tup.expression)
|
||||
.Where(g => CoreExpressionEvaluator<PNLRowData>.validate(g.Key))
|
||||
.Select(g => (
|
||||
expression: g.Key,
|
||||
animations: g.ToArray()))
|
||||
.ToList();
|
||||
|
||||
var normalized_anims = trigger_anims
|
||||
.Where(tup => CoreExpressionEvaluator<PNLNormalizedData>.validate(tup.expression))
|
||||
.ToList();
|
||||
|
||||
s = s.with(anchors: anchor_hierarcy.value,
|
||||
grid: new_cols.IsValueCreated ? s.grid.with(columns: new_cols.Value) : null,
|
||||
group_columns: columns.Select(c => {
|
||||
var col_settings = c.create();
|
||||
col_settings = c.default_cell_appearance.group_vm.apply_to(col_settings);
|
||||
return col_settings;
|
||||
}),
|
||||
header_columns: columns.Select(c => {
|
||||
var col_settings = c.create();
|
||||
col_settings = c.default_cell_appearance.header_vm.apply_to(col_settings);
|
||||
return col_settings;
|
||||
}),
|
||||
animations: trigger_anims.Select(
|
||||
ta => new PNLAnimationTriggerData {
|
||||
expression = ta.expression,
|
||||
animations = Array.ConvertAll(ta.animations, t => t.row)
|
||||
}),
|
||||
group_header_animations: normalized_anims
|
||||
.Select(
|
||||
ta => new PNLAnimationTriggerData {
|
||||
expression = ta.expression,
|
||||
animations = Array.ConvertAll(ta.animations, t => t.group)
|
||||
}),
|
||||
header_row_animations: normalized_anims.Select(
|
||||
ta => new PNLAnimationTriggerData {
|
||||
expression = ta.expression,
|
||||
animations = Array.ConvertAll(ta.animations, t => t.header)
|
||||
}));
|
||||
s.header_row_display = header_row_defaults.display;
|
||||
s.header_cell_appearance = PNLCellAppearanceProto.from_appearance(header_row_defaults.to_appearance());
|
||||
// group
|
||||
var (group_cell_appearance, group_row_background_colors) = group_row_defaults.create();
|
||||
s.group_cell_appearance = PNLCellAppearanceProto.from_appearance(group_cell_appearance);
|
||||
s.group_background_colors.SetItems(group_row_background_colors.Select(c => c.rgb_color()));
|
||||
//
|
||||
return s;
|
||||
}
|
||||
|
||||
public void load(PNLSettings value) {
|
||||
_last_loaded = value;
|
||||
header_row_defaults.load(value.header_cell_appearance.to_appearance());
|
||||
group_row_defaults.load((value.group_cell_appearance.to_appearance(), value.group_background_colors.WrapSelectorRO(c => c.argb_color())));
|
||||
var column_map = _map_column_data(value.grid.columns, value.animations);
|
||||
var group_column_map = _map_column_data(value.group_columns, value.group_header_animations);
|
||||
var header_column_map = _map_column_data(value.header_columns, value.header_row_animations);
|
||||
|
||||
core_settings_vm.load(value.grid, false);
|
||||
using (core_settings_vm.begin_change_columns()) {
|
||||
core_settings_vm.columns.SetItems(value.grid.columns.Select(c => {
|
||||
var key = EnumHelper.parse<ColumnKeys>(c.id);
|
||||
var vm = new PNLColumnSettingsVM(key);
|
||||
var (col_settings, trigger_animations) = column_map[key];
|
||||
var group_data = group_column_map.GetNullable(key);
|
||||
var header_data = header_column_map.GetNullable(key);
|
||||
vm.load_from(col_settings, trigger_animations, group_data?.settings, group_data?.triggers, header_data?.triggers, header_data?.settings);
|
||||
return vm;
|
||||
}));
|
||||
}
|
||||
anchor_hierarcy.value = value.anchors.Intersect(PNLSettings.anchorable_columns);
|
||||
header_row_defaults.display = value.header_row_display;
|
||||
calculation_type = value.calculation_type;
|
||||
}
|
||||
|
||||
PNLSettings IValueFactory<PNLSettings>.create() => get_result();
|
||||
void IValueFactory<PNLSettings>.load_from(PNLSettings value) => load(value);
|
||||
IDisposable IObservable<PNLSettings>.Subscribe(IObserver<PNLSettings> observer) => throw new NotImplementedException();
|
||||
|
||||
static Dictionary<ColumnKeys, (GridColumnSettings settings, Dictionary<string, PNLAnimation> triggers)> _map_column_data(IEnumerable<GridColumnSettings> columns, IEnumerable<PNLAnimationTriggerData> triggers) {
|
||||
var map = columns.ToDictionary(
|
||||
c => c.strong_id<ColumnKeys>(),
|
||||
c => (settings: c, triggers: new Dictionary<string, PNLAnimation>()));
|
||||
foreach (var trig in triggers) {
|
||||
foreach (var anim in trig.animations) {
|
||||
if (map.TryGetValue(anim.column, out var col_data))
|
||||
col_data.triggers[trig.expression] = anim;
|
||||
}
|
||||
}
|
||||
return map;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -291,7 +291,7 @@ namespace bts.shell.pnl {
|
||||
};
|
||||
|
||||
public void load_from(PNLSettings settings) {
|
||||
selection_highlight = settings.grid.selection_highlight.to_brush();
|
||||
selection_highlight = settings.grid.selection_highlight.to_argb_brush();
|
||||
grid_background_color = settings.grid.background;
|
||||
calculation_type = settings.calculation_type;
|
||||
if (settings.filters != null) {
|
||||
@@ -383,7 +383,7 @@ namespace bts.shell.pnl {
|
||||
// hide implicit cols
|
||||
state.grid = _hide_cols(state.grid, anchors);
|
||||
return new FillsVM(
|
||||
_bus, _orders_model, Observable.Return(state), new WindowSettingsManager<GridSettings>.DisconnectedImpl(state.grid));
|
||||
_bus, _orders_model, state, new WindowSettingsManager<GridSettings>.DisconnectedImpl(state.grid));
|
||||
}
|
||||
|
||||
public FillsVM mk_fills_vm(PNLRowCollectionViewGroup group, ColumnKeys key) {
|
||||
@@ -393,7 +393,7 @@ namespace bts.shell.pnl {
|
||||
// hide implicit cols
|
||||
state.grid = _hide_cols(state.grid, anchors.WrapSubRange(0, group.level));
|
||||
return new FillsVM(
|
||||
_bus, _orders_model, Observable.Return(state), _visible_fills_source(), new WindowSettingsManager<GridSettings>.DisconnectedImpl(state.grid));
|
||||
_bus, _orders_model, state, _visible_fills_source(), new WindowSettingsManager<GridSettings>.DisconnectedImpl(state.grid));
|
||||
}
|
||||
|
||||
public FillsVM mk_fills_vm(PNLHeaderRowDisplay header, ColumnKeys key) {
|
||||
@@ -419,7 +419,7 @@ namespace bts.shell.pnl {
|
||||
break;
|
||||
}
|
||||
return new FillsVM(
|
||||
_bus, _orders_model, Observable.Return(state), src, new WindowSettingsManager<GridSettings>.DisconnectedImpl(state.grid));
|
||||
_bus, _orders_model, state, src, new WindowSettingsManager<GridSettings>.DisconnectedImpl(state.grid));
|
||||
}
|
||||
|
||||
public OrderBookVM mk_orderbook_vm(PNLHeaderRowDisplay header, ColumnKeys key) {
|
||||
|
||||
@@ -0,0 +1,174 @@
|
||||
<UserControl
|
||||
x:Class="mbts.ui.spark.PNLSettingsView"
|
||||
x:ClassModifier="internal"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:pnl="clr-namespace:bts.shell.pnl"
|
||||
xmlns:u="clr-namespace:mbts.ui"
|
||||
xmlns:c="clr-namespace:mbts.ui.controls"
|
||||
xmlns:cv="clr-namespace:mbts.ui.converters"
|
||||
xmlns:usp="clr-namespace:mbts.ui.spark"
|
||||
xmlns:vm="clr-namespace:mbts.ui.viewmodels"
|
||||
Content="{Binding}"
|
||||
>
|
||||
<UserControl.Resources>
|
||||
<ResourceDictionary>
|
||||
<ResourceDictionary.MergedDictionaries>
|
||||
<usp:GridSettingsResources/>
|
||||
</ResourceDictionary.MergedDictionaries>
|
||||
|
||||
<DataTemplate DataType="{x:Type pnl:PNLColumnSettingsVM}">
|
||||
<ItemsControl ItemsSource="{Binding parameters}"/>
|
||||
</DataTemplate>
|
||||
|
||||
<DataTemplate DataType="{x:Type vm:OrderedItemParameterVM}">
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<CheckBox x:Name="IncludeToggle" VerticalAlignment="Top" Margin="0,4,0,0" Width="22" IsChecked="{Binding value}">
|
||||
<CheckBox.Height>
|
||||
<Binding Path="ActualHeight" ElementName="AnchorBorder">
|
||||
<Binding.Converter>
|
||||
<!--Offset Margin.Top-->
|
||||
<cv:DoubleSubtractionConverter Value="4"/>
|
||||
</Binding.Converter>
|
||||
</Binding>
|
||||
</CheckBox.Height>
|
||||
</CheckBox>
|
||||
<Border x:Name="AnchorBorder" Grid.Column="1" BorderBrush="Transparent" BorderThickness="0,4,4,4" MinWidth="120" Padding="6,8" Cursor="{StaticResource {x:Static u:CursorsEx.GrabKey}}">
|
||||
<u:StateManager.Groups>
|
||||
<u:StateGroupsManager AttachToMembers="True">
|
||||
<u:StateDefinitionCollection>
|
||||
<u:MouseStateDefinition MouseSource="{Binding Group.Target, RelativeSource={RelativeSource Self}}"/>
|
||||
<u:CheckedStateDefinition Source="{u:DirectReference IncludeToggle}"/>
|
||||
</u:StateDefinitionCollection>
|
||||
</u:StateGroupsManager>
|
||||
</u:StateManager.Groups>
|
||||
<VisualStateManager.VisualStateGroups>
|
||||
<VisualStateGroup Name="MouseStates">
|
||||
<VisualState Name="Normal"/>
|
||||
<VisualState Name="MouseOver"/>
|
||||
<VisualState Name="Pressed">
|
||||
<Storyboard>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="Cursor">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="{StaticResource {x:Static u:CursorsEx.GrabbingKey}}"/>
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
<ColorAnimation Storyboard.TargetProperty="(Panel.Background).(SolidColorBrush.Color)" Duration="0" To="{x:Static u:BTSColors.header_alternate_color}"/>
|
||||
</Storyboard>
|
||||
</VisualState>
|
||||
</VisualStateGroup>
|
||||
<VisualStateGroup Name="CheckedStates">
|
||||
<VisualState Name="Unchecked"/>
|
||||
<VisualState Name="Checked">
|
||||
<Storyboard>
|
||||
<DoubleAnimation Storyboard.TargetName="KeyIcon" Storyboard.TargetProperty="Opacity" BeginTime="0" Duration="0" To="1"/>
|
||||
<DoubleAnimation Storyboard.TargetName="AnchorIndex" Storyboard.TargetProperty="Opacity" BeginTime="0" Duration="0" To="1"/>
|
||||
<ColorAnimation Storyboard.TargetName="ColumnNameText" Storyboard.TargetProperty="Foreground.Color" BeginTime="0" Duration="0" To="Gold"/>
|
||||
</Storyboard>
|
||||
</VisualState>
|
||||
</VisualStateGroup>
|
||||
</VisualStateManager.VisualStateGroups>
|
||||
<Border.Resources>
|
||||
<u:CursorsEx/>
|
||||
</Border.Resources>
|
||||
<Border.Background>
|
||||
<SolidColorBrush Color="{x:Static u:BTSColors.header_color}"/>
|
||||
</Border.Background>
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<Image x:Name="KeyIcon" Height="11" Opacity="0">
|
||||
<Image.Source>
|
||||
<DrawingImage>
|
||||
<DrawingImage.Drawing>
|
||||
<GeometryDrawing Geometry="{Binding Source={StaticResource {x:Static u:Icons.KeyGeometryKey}}}" Brush="Gold"/>
|
||||
</DrawingImage.Drawing>
|
||||
</DrawingImage>
|
||||
</Image.Source>
|
||||
</Image>
|
||||
<TextBlock x:Name="AnchorIndex" HorizontalAlignment="Left" VerticalAlignment="Bottom" Margin="16,0,0,-2" FontSize="8.5" Foreground="Gold" Opacity="0">
|
||||
<TextBlock.Text>
|
||||
<Binding Path="checked_index">
|
||||
<Binding.Converter>
|
||||
<cv:IntAdditionConverter Value="1"/>
|
||||
</Binding.Converter>
|
||||
</Binding>
|
||||
</TextBlock.Text>
|
||||
</TextBlock>
|
||||
<TextBlock x:Name="ColumnNameText" Grid.Column="1" Margin="6,0,0,0" HorizontalAlignment="Left" VerticalAlignment="Center" FontWeight="Bold" Text="{Binding}">
|
||||
<TextBlock.Foreground>
|
||||
<SolidColorBrush Color="{x:Static u:BTSColors.header_text_color}"/>
|
||||
</TextBlock.Foreground>
|
||||
</TextBlock>
|
||||
</Grid>
|
||||
</Border>
|
||||
</Grid>
|
||||
</DataTemplate>
|
||||
|
||||
<DataTemplate DataType="{x:Type pnl:PNLAnchorHierarchyVM}">
|
||||
<Grid Margin="12,8">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="*"/>
|
||||
</Grid.RowDefinitions>
|
||||
<TextBlock FontStyle="Italic">
|
||||
<Run>
|
||||
Check the groups you would like to use.
|
||||
</Run>
|
||||
<LineBreak/>
|
||||
<Run>
|
||||
Drag and drop them to change the grouping order.
|
||||
</Run>
|
||||
</TextBlock>
|
||||
<ItemsControl
|
||||
Grid.Row="1"
|
||||
AllowDrop="True"
|
||||
HorizontalAlignment="Left"
|
||||
ItemsSource="{Binding items}"
|
||||
Margin="0,8,0,0"
|
||||
Loaded="_anchor_items_loaded"
|
||||
>
|
||||
</ItemsControl>
|
||||
</Grid>
|
||||
</DataTemplate>
|
||||
</ResourceDictionary>
|
||||
</UserControl.Resources>
|
||||
<UserControl.ContentTemplate>
|
||||
<DataTemplate DataType="{x:Type pnl:PNLSettingsVM}">
|
||||
<Grid>
|
||||
<u:BindingProxy x:Name="ActionMenuItemsViewProxy" Value="{DynamicResource ActionMenuItemsView}"/>
|
||||
<c:ActionMenu ItemsSource="{Binding Path='(u:BindingProxy.Value).(CollectionViewSource.View)', ElementName=ActionMenuItemsViewProxy}">
|
||||
<ItemsControl.GroupStyle>
|
||||
<GroupStyle ContainerStyle="{StaticResource ComboBox.DelineatedGroupContainerStyle.DynamicFill}"/>
|
||||
</ItemsControl.GroupStyle>
|
||||
</c:ActionMenu>
|
||||
<TabControl MinWidth="600" Margin="0,2,0,0" Style="{StaticResource FlatTabControl}">
|
||||
<TabItem Header="Grid">
|
||||
<ItemsControl
|
||||
Grid.IsSharedSizeScope="True"
|
||||
ItemsSource="{Binding core_settings_vm.parameters}"
|
||||
/>
|
||||
</TabItem>
|
||||
<TabItem Header="Grouping" Content="{Binding anchor_hierarcy}"/>
|
||||
<TabItem Header="Group Row Display">
|
||||
<ItemsControl
|
||||
Grid.IsSharedSizeScope="True"
|
||||
ItemsSource="{Binding group_row_defaults.parameters}"
|
||||
/>
|
||||
</TabItem>
|
||||
<TabItem Header="Header Row Display">
|
||||
<ItemsControl
|
||||
Grid.IsSharedSizeScope="True"
|
||||
ItemsSource="{Binding header_row_defaults.parameters}"
|
||||
/>
|
||||
</TabItem>
|
||||
<TabItem Header="Columns" Content="{Binding core_settings_vm}" ContentTemplate="{StaticResource ColumnsTabContent}"/>
|
||||
</TabControl>
|
||||
</Grid>
|
||||
</DataTemplate>
|
||||
</UserControl.ContentTemplate>
|
||||
</UserControl>
|
||||
@@ -0,0 +1,129 @@
|
||||
using bts;
|
||||
using mbts.ui.controls.adorners;
|
||||
using mbts.ui.extensions;
|
||||
using mbts.utils;
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Reactive.Disposables;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Media;
|
||||
|
||||
namespace mbts.ui.spark {
|
||||
/// <summary>
|
||||
/// Interaction logic for PNLSettingsView.xaml
|
||||
/// </summary>
|
||||
partial class PNLSettingsView : UserControl {
|
||||
public PNLSettingsView() =>
|
||||
InitializeComponent();
|
||||
|
||||
IDisposable _anchor_items_drag_drop_behavior;
|
||||
|
||||
void _anchor_items_loaded(object sender, RoutedEventArgs e) {
|
||||
// connecting this drag/drop behavior is idempotent, just in case Loaded happens twice
|
||||
Disposable.dispose(ref _anchor_items_drag_drop_behavior);
|
||||
if (!(sender is ItemsControl ic))
|
||||
return;
|
||||
var root_grid = Window.GetWindow(ic)?.Content as Grid;
|
||||
bool is_dragging = false;
|
||||
IDisposable drag_op = null;
|
||||
var cursor_resources = new Lazy<CursorsEx>();
|
||||
_anchor_items_drag_drop_behavior = Disposable.Create(() => Disposable.dispose(ref drag_op));
|
||||
DragDropHelper.subscribe_drag_start(ic).Subscribe(pt => {
|
||||
if (!is_dragging &&
|
||||
ic.InputHitTest(pt) is DependencyObject d &&
|
||||
ic.ContainerFromElement(d) is FrameworkElement item_view &&
|
||||
ic.ItemContainerGenerator.ItemFromContainer(item_view) is object item &&
|
||||
item != DependencyProperty.UnsetValue) {
|
||||
//
|
||||
item_view.SetCurrentValue(OpacityProperty, 0.5);
|
||||
is_dragging = true;
|
||||
var sz = item_view.RenderSize;
|
||||
var dv = new DrawingVisual();
|
||||
using (var dc = dv.RenderOpen())
|
||||
dc.DrawRoundedRectangle(new VisualBrush(item_view) { Stretch = Stretch.Uniform }, null, new Rect(sz), 0, 0);
|
||||
SetCurrentValue(CursorProperty, Cursors.ScrollWE);
|
||||
SetCurrentValue(AllowDropProperty, true);
|
||||
is_dragging = true;
|
||||
Disposable.replace(ref drag_op, new CompositeDisposable {
|
||||
MouseTraceAdorner.trace_drag(root_grid, dv, sz),
|
||||
DragDropHelper.connect_vertical_insertion_adorner(ic, root_grid),
|
||||
this.SubscribePreviewDrop(on_drop),
|
||||
this.SubscribePreviewMouseUp(on_preview_mouse_up),
|
||||
this.SubscribePreviewDragOver(on_drag),
|
||||
this.SubscribePreviewDragLeave(on_drag),
|
||||
this.SubscribePreviewDragEnter(on_drag),
|
||||
() => {
|
||||
ClearValue(CursorProperty);
|
||||
ClearValue(AllowDropProperty);
|
||||
item_view.ClearValue(OpacityProperty);
|
||||
is_dragging = false;
|
||||
}});
|
||||
DragDrop.DoDragDrop(this, new DataObject(DataFormats.StringFormat, item), DragDropEffects.Move);
|
||||
}
|
||||
});
|
||||
|
||||
void on_drag(object s, DragEventArgs de) =>
|
||||
de.Effects = DragDropEffects.Move;
|
||||
|
||||
void on_drop(object _, DragEventArgs drop_e) {
|
||||
drop_e.Handled = true;
|
||||
if (!is_dragging)
|
||||
return;
|
||||
Disposable.dispose(ref drag_op);
|
||||
if (drop_e.Data.GetDataPresent(DataFormats.StringFormat) &&
|
||||
drop_e.Data.GetData(DataFormats.StringFormat) is object from &&
|
||||
ic.ItemsSource is IList anchor_items &&
|
||||
anchor_items.Count > 1) {
|
||||
//
|
||||
var pt = drop_e.GetPosition(this);
|
||||
if (!(InputHitTest(pt) is DependencyObject hit))
|
||||
return;
|
||||
try {
|
||||
int from_i = anchor_items.IndexOf(from);
|
||||
// Set the container to the first item who's right edge is left of pt.X (or the last item if none satisfy that condition).
|
||||
// The logic presumes that the items are laid out in a single, horizontal line, from left to right
|
||||
// (not the most elegant way to get the drop target, but it's effective).
|
||||
int to_i = -1;
|
||||
if (!(ic.ContainerFromElement(hit) is FrameworkElement drop_target)) {
|
||||
for (to_i = 0; to_i < ic.Items.Count; to_i++) {
|
||||
if (ic.ItemContainerGenerator.ContainerFromIndex(to_i) is FrameworkElement c) {
|
||||
drop_target = c;
|
||||
var tl = c.TranslatePoint(new Point(0, 0), this);
|
||||
var bottom_edge = tl.Y + c.ActualHeight;
|
||||
if (bottom_edge >= pt.Y)
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else if (drop_target.DataContext is object to) {
|
||||
to_i = anchor_items.IndexOf(to);
|
||||
var drop_target_pt = TranslatePoint(pt, drop_target);
|
||||
if (drop_target_pt.Y > drop_target.ActualHeight / 2) {
|
||||
// target is "Between drop_target and the item below it"
|
||||
if (from_i > to_i)
|
||||
to_i++;
|
||||
} else {
|
||||
// target is "Between drop_target and the item above it"
|
||||
if (from_i < to_i) {
|
||||
to_i--;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (to_i < 0)
|
||||
return;
|
||||
if (to_i >= anchor_items.Count)
|
||||
to_i = anchor_items.Count - 1;
|
||||
if (from_i >= 0)
|
||||
anchor_items.Move(from_i, to_i);
|
||||
} catch (Exception ex) {
|
||||
Log.Ui.warn($"Error dropping anchor hiearchy item in pnl settings dialog: {ex}.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void on_preview_mouse_up(object _, MouseButtonEventArgs __) =>
|
||||
Disposable.dispose(ref drag_op);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,65 +1,15 @@
|
||||
using bts.api;
|
||||
using bts.api.orderman;
|
||||
using bts.api.orderman;
|
||||
using bts.shell;
|
||||
using bts.utils;
|
||||
using mbts.spark;
|
||||
using mbts.ui;
|
||||
using mbts.ui.settings;
|
||||
using mbts.ui.utility;
|
||||
using mbts.utils;
|
||||
using ProtoBuf;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reactive.Linq;
|
||||
using OrderType = bts.api.orderman.CustomOrderDefinition.CustomOneofCase;
|
||||
using System.ComponentModel;
|
||||
using PTOC = mbts.OrderRecordTuplePredicate.PredicateTypeOneofCase;
|
||||
|
||||
namespace mbts {
|
||||
static class OrderRecordTuplePredicateVMFactory {
|
||||
// TODO: uncomment the remaining cases when ready
|
||||
public static Dictionary<PTOC, ITemplatedPropertyWrapperVM<OrderRecordTuplePredicate>> get_oneofs(IBus bus, OrdersModel orders_model) {
|
||||
return new Dictionary<PTOC, ITemplatedPropertyWrapperVM<OrderRecordTuplePredicate>> {
|
||||
{ PTOC.FilledEvent, wrap(rp => rp.filled_event, (rp, p) => rp.filled_event = p, p => p.predicate_type_case, OrderFilledEventPredicateVMFactory.get_oneofs(bus)) },
|
||||
{ PTOC.CompletedEvent, wrap(rp => rp.completed_event, (rp, p) => rp.completed_event = p, p => p.predicate_type_case, OrderCompletedEventPredicateVMFactory.get_oneofs(bus)) },
|
||||
{ PTOC.CanceledEvent, wrap(rp => rp.canceled_event, (rp, p) => rp.canceled_event = p, p => p.predicate_type_case, OrderCanceledEventPredicateVMFactory.get_oneofs(bus)) },
|
||||
//{ PTOC.Security, wrap(rp => rp.security, (rp, p) => rp.security = p, p => p.predicate_type_case, SecurityCriteriaVMFactory.get_oneofs(bus)) },
|
||||
//{ PTOC.Time, wrap(rp => rp.time_created, (rp, p) => rp.time_created = p, t => t.predicate_type_case, TimePredicateVMFactory.get_oneofs()) },
|
||||
//{ PTOC.LastChange, wrap(rp => rp.last_change, (rp, p) => rp.last_change = p, t => t.predicate_type_case, TimePredicateVMFactory.get_oneofs()) },
|
||||
//{ PTOC.User, wrap(rp => rp.user, (rp, p) => rp.user = p, p => p.predicate_type_case, DiscreteValuesPredicateVMFactory.get_oneofs<uint, uint, uint?, uint[], DiscreteUIntPredicate>(
|
||||
// RXCollection.Create(orders_model.factory.subscribe_user_ids().Select(tups => tups.Select(convert_name_id))), id => id, id => id.Value, id => id, ids => ids, ids => ids.ToArray())) },
|
||||
//{ PTOC.Account, wrap(rp => rp.account, (rp, p) => rp.account = p, p => p.predicate_type_case, DiscreteValuesPredicateVMFactory.get_oneofs<uint, uint, uint?, uint[], DiscreteUIntPredicate>(
|
||||
// RXCollection.Create(orders_model.factory.subscribe_account_ids().Select(tups => tups.Select(convert_name_id))), id => id, id => id.Value, id => id, ids => ids, ids => ids.ToArray())) },
|
||||
//{ PTOC.Exchange, wrap(rp => rp.exchange, (rp, p) => rp.exchange = p, p => p.predicate_type_case, DiscreteValuesPredicateVMFactory.get_oneofs_value_type<Venue, ExchangePredicate>(EnumHelper.get_values_except(Venue.Unset))) },
|
||||
//{ PTOC.TimeInForce, wrap(rp => rp.time_in_force, (rp, p) => rp.time_in_force = p, p => p.predicate_type_case, DiscreteValuesPredicateVMFactory.get_oneofs_value_type<TimeInForce, TimeInForcePredicate>(EnumHelper.get_values_except(TimeInForce.Undefined))) },
|
||||
//{ PTOC.Side, wrap(rp => rp.side, (rp, p) => rp.side = p, p => p.predicate_type_case, SidePredicateVMFactory.get_oneofs()) },
|
||||
//{ PTOC.Price, wrap(rp => rp.price, (rp, p) => rp.price = p, p => p.predicate_type_case, DoublePredicateVMFactory.get_oneofs()) },
|
||||
//{ PTOC.Size, wrap(rp => rp.quantity, (rp, p) => rp.quantity = p, p => p.predicate_type_case, UIntPredicateVMFactory.get_oneofs()) },
|
||||
//{ PTOC.Filled, wrap(rp => rp.filled_quantity, (rp, p) => rp.filled_quantity = p, p => p.predicate_type_case, UIntPredicateVMFactory.get_oneofs()) },
|
||||
//{ PTOC.AvgFilled, wrap(rp => rp.avg_price, (rp, p) => rp.avg_price = p, p => p.predicate_type_case, DoublePredicateVMFactory.get_oneofs()) },
|
||||
//{ PTOC.LastPrice, wrap(rp => rp.last_price, (rp, p) => rp.last_price = p, p => p.predicate_type_case, DoublePredicateVMFactory.get_oneofs()) },
|
||||
//{ PTOC.OrderType, wrap(rp => rp.order_type, (rp, p) => rp.order_type = p, p => p.predicate_type_case, DiscreteValuesPredicateVMFactory.get_oneofs_value_type<OrderType, OrderTypePredicate>(EnumHelper.get_values_except(OrderType.None))) },
|
||||
//{ PTOC.SubType, wrap(rp => rp.order_sub_type, (rp, p) => rp.order_sub_type = p, p => p.predicate_type_case, DiscreteValuesPredicateVMFactory.get_oneofs_value_type<OrderSubType, OrderSubTypePredicate>(EnumHelper.get_values<OrderSubType>())) },
|
||||
//{ PTOC.Status, wrap(rp => rp.order_status, (rp, p) => rp.order_status = p, p => p.predicate_type_case, DiscreteValuesPredicateVMFactory.get_oneofs_value_type<OrderStatus, OrderStatusPredicate>(EnumHelper.get_values<OrderStatus>())) },
|
||||
//{ PTOC.LastMessage, wrap(rp => rp.last_message, (rp, p) => rp.last_message = p, p => p.predicate_type_case, StringPredicateVMFactory.get_oneofs()) },
|
||||
//{ PTOC.SecurityKey, wrap(rp => rp.security_key, (rp, p) => rp.security_key = p, p => p.predicate_type_case, UIntPredicateVMFactory.get_oneofs()) },
|
||||
//{ PTOC.OrderKey, wrap(rp => rp.order_key, (rp, p) => rp.order_key = p, p => p.predicate_type_case, UIntPredicateVMFactory.get_oneofs()) },
|
||||
//{ PTOC.ParentKey, wrap(rp => rp.parent_key, (rp, p) => rp.parent_key = p, p => p.predicate_type_case, UIntPredicateVMFactory.get_oneofs()) },
|
||||
};
|
||||
|
||||
//IViewValue<uint> convert_name_id((string name, uint id) tup) =>
|
||||
// new ViewValueTuple<uint>(tup.id, tup.name);
|
||||
|
||||
ITemplatedPropertyWrapperVM<OrderRecordTuplePredicate> wrap<TChildPredicate, TChildPredicateOneof>(
|
||||
Func<OrderRecordTuplePredicate, TChildPredicate> get,
|
||||
Action<OrderRecordTuplePredicate, TChildPredicate> set,
|
||||
Func<TChildPredicate, TChildPredicateOneof> key,
|
||||
Dictionary<TChildPredicateOneof, ITemplatedPropertyWrapperVM<TChildPredicate>> oneofs) where TChildPredicateOneof : struct where TChildPredicate : new() =>
|
||||
//
|
||||
PredicateVMWrapper.wrap_child_oneof<OrderRecordTuplePredicate, TChildPredicate, TChildPredicateOneof>(get, set, key, oneofs);
|
||||
}
|
||||
}
|
||||
|
||||
[ProtoContract]
|
||||
class OrderRecordTuplePredicate : IEquatable<OrderRecordTuplePredicate> {
|
||||
SecurityPredicate _security;
|
||||
@@ -120,11 +70,11 @@ namespace mbts {
|
||||
OrderKey,
|
||||
[PredicateCase(is_toastable:false)]
|
||||
ParentKey,
|
||||
[PredicateCase("When order gets a fill", PredicateCategory.Event)]
|
||||
[PredicateCase("Order Filled", PredicateCategory.Event)]
|
||||
FilledEvent,
|
||||
[PredicateCase("When order is completed", PredicateCategory.Event)]
|
||||
[PredicateCase("Order Completed", PredicateCategory.Event)]
|
||||
CompletedEvent,
|
||||
[PredicateCase("When order is canceled", PredicateCategory.Event)]
|
||||
[PredicateCase("Order Canceled", PredicateCategory.Event)]
|
||||
CanceledEvent,
|
||||
[PredicateCase(is_toastable:false)]
|
||||
AttachedTargetOrderKey
|
||||
@@ -363,6 +313,7 @@ namespace mbts {
|
||||
|
||||
[ProtoContract]
|
||||
class OrderRecordTuplePredicateAnimations : IPredicateAnimationsData<OrderSecurity, OrderRecordTuplePredicate> {
|
||||
const bool _default_is_enabled = true;
|
||||
[ProtoMember(1)]
|
||||
OrderRecordTuplePredicate _predicate;
|
||||
[ProtoMember(2)]
|
||||
@@ -371,19 +322,31 @@ namespace mbts {
|
||||
List<RowAnimation> _row_animations;
|
||||
[ProtoMember(4)]
|
||||
List<SoundNotification> _sound_animations;
|
||||
// is_enabled
|
||||
[ProtoMember(5)]
|
||||
[DefaultValue(_default_is_enabled)]
|
||||
bool _is_enabled;
|
||||
|
||||
public OrderRecordTuplePredicateAnimations() { }
|
||||
public OrderRecordTuplePredicateAnimations() =>
|
||||
_is_enabled = _default_is_enabled;
|
||||
|
||||
public OrderRecordTuplePredicateAnimations(OrderRecordTuplePredicate predicate, IEnumerable<CellAnimation> cell_animations, IEnumerable<RowAnimation> row_animations, IEnumerable<SoundNotification> sound_animations) {
|
||||
public OrderRecordTuplePredicateAnimations(
|
||||
OrderRecordTuplePredicate predicate,
|
||||
IEnumerable<CellAnimation> cell_animations,
|
||||
IEnumerable<RowAnimation> row_animations,
|
||||
IEnumerable<SoundNotification> sound_animations,
|
||||
bool is_enabled = true) {
|
||||
_predicate = predicate;
|
||||
_cell_animations = new List<CellAnimation>(cell_animations);
|
||||
_row_animations = new List<RowAnimation> ( row_animations );
|
||||
_sound_animations = new List<SoundNotification> ( sound_animations );
|
||||
_cell_animations = new List<CellAnimation>(cell_animations.empty_if_null());
|
||||
_row_animations = new List<RowAnimation>(row_animations.empty_if_null());
|
||||
_sound_animations = new List<SoundNotification>(sound_animations.empty_if_null());
|
||||
_is_enabled = is_enabled;
|
||||
}
|
||||
|
||||
public OrderRecordTuplePredicate predicate_data => _predicate;
|
||||
public IReadOnlyList<CellAnimation> cell_animations => _cell_animations ?? (_cell_animations = new List<CellAnimation>());
|
||||
public IReadOnlyList<RowAnimation> row_animations => _row_animations ?? (_row_animations = new List<RowAnimation>());
|
||||
public IReadOnlyList<SoundNotification> sound_notifications => _sound_animations ?? (_sound_animations = new List<SoundNotification>());
|
||||
public bool is_enabled => _is_enabled;
|
||||
}
|
||||
}
|
||||
@@ -41,9 +41,12 @@
|
||||
<ResourceDictionary Source="/view/Controls/DataGridEx/Filters/Views/DoubleMinMaxFilterControl.xaml"/>
|
||||
<ResourceDictionary Source="/view/Controls/DataGridEx/Filters/Views/DiscreteValuesFilterControl.xaml"/>
|
||||
<ResourceDictionary Source="/view/Controls/DataGridEx/Filters/Views/TimeFilterControl.xaml"/>
|
||||
<ResourceDictionary Source="/view/Controls/ActionMenu/ActionMenu.xaml"/>
|
||||
<ResourceDictionary Source="/Modules/Spreader/ExecutionStrategyEditor.xaml"/>
|
||||
<ResourceDictionary Source="/view/Controls/Order/AlgoContentTemplateSelector.xaml"/>
|
||||
<ResourceDictionary Source="/view/Controls/Order/AlgoPickerNodeView.xaml"/>
|
||||
<ResourceDictionary Source="/view/Controls/Popup/PopupControl.xaml"/>
|
||||
<ResourceDictionary Source="/view/Controls/ColorPicker/TabularColorPicker.xaml"/>
|
||||
</ResourceDictionary.MergedDictionaries>
|
||||
|
||||
<Style TargetType="ScrollViewer">
|
||||
@@ -559,4 +562,4 @@
|
||||
<Setter Property="Command" Value="{Binding command}"/>
|
||||
<Setter Property="Icon" Value="{Binding icon, Converter={StaticResource IconConverter}}"/>
|
||||
</Style>
|
||||
</ResourceDictionary>
|
||||
</ResourceDictionary>
|
||||
@@ -115,20 +115,29 @@ namespace mbts.ui.controls {
|
||||
}
|
||||
}
|
||||
|
||||
internal class Aggregator<T> : IDisposable, IList<(IReadOnlyCollection<T> items, INotifyCollectionChanged notifier)>, IReadOnlyList<(IReadOnlyCollection<T> items, INotifyCollectionChanged notifier)> {
|
||||
// TODO: simplifiy this class to implement IList<IObservableCollection<T>>, or something other than the tuple
|
||||
// it is proving to be more trouble than its worth; the onus should be on callers to wrap/convert their collections if they do not implement IObservableCollection<T>.
|
||||
//internal class Aggregator<T> : IDisposable, IList<(IReadOnlyCollection<T> items, INotifyCollectionChanged notifier)>, IReadOnlyList<(IReadOnlyCollection<T> items, INotifyCollectionChanged notifier)> {
|
||||
internal class Aggregator<T> : IDisposable, IReadOnlyList<(IReadOnlyCollection<T> items, INotifyCollectionChanged notifier)> {
|
||||
readonly ObservableCollection<T> _target;
|
||||
readonly List<(IReadOnlyCollection<T> items, INotifyCollectionChanged notifier)> _collections = new List<(IReadOnlyCollection<T> items, INotifyCollectionChanged notifier)>();
|
||||
readonly List<NotifierCollectionWrapper> _collections = new List<NotifierCollectionWrapper>();
|
||||
readonly IList<(IReadOnlyCollection<T> items, INotifyCollectionChanged notifier)> _collection_tuples;
|
||||
bool _is_disposed;
|
||||
|
||||
public Aggregator(ObservableCollection<T> target, params (IReadOnlyCollection<T> items, INotifyCollectionChanged notifier)[] collections) {
|
||||
_collection_tuples = _collections.WrapSelector(nc => nc.tuple, null);
|
||||
_target = target;
|
||||
foreach (var c in collections)
|
||||
Add(c);
|
||||
}
|
||||
|
||||
public (IReadOnlyCollection<T> items, INotifyCollectionChanged notifier) this[int index] {
|
||||
get => _collections[index];
|
||||
set => Insert(index, value);
|
||||
get => _collections[index].tuple;
|
||||
set {
|
||||
// TODO: optimize with 'Replace' ops instead of Remove/Add
|
||||
RemoveAt(index);
|
||||
Insert(index, value);
|
||||
}
|
||||
}
|
||||
|
||||
public int Count => _collections.Count;
|
||||
@@ -138,16 +147,24 @@ namespace mbts.ui.controls {
|
||||
public void Add((IReadOnlyCollection<T> items, INotifyCollectionChanged notifier) collection) {
|
||||
_check_disposed();
|
||||
_target.AddRange(collection.items);
|
||||
_collections.Add(collection);
|
||||
collection.notifier.CollectionChanged += _on_collection_changed;
|
||||
var wrapper = new NotifierCollectionWrapper(collection);
|
||||
_collections.Add(wrapper);
|
||||
wrapper.CollectionChanged += _on_collection_changed;
|
||||
}
|
||||
|
||||
public bool Remove((IReadOnlyCollection<T> items, INotifyCollectionChanged notifier) collection) {
|
||||
int num_before = _collections.Count;
|
||||
_check_disposed();
|
||||
_remove_collection_items(collection, out int _, out int collection_index);
|
||||
_remove_collection_items(collection, out int _, out int collection_index, true);
|
||||
_collections.RemoveAt(collection_index);
|
||||
return _collections.Count < num_before;
|
||||
}
|
||||
|
||||
bool _remove(NotifierCollectionWrapper wrapper) {
|
||||
int num_before = _collections.Count;
|
||||
_check_disposed();
|
||||
_remove_collection_items(wrapper, out int _, out int collection_index, true);
|
||||
_collections.RemoveAt(collection_index);
|
||||
collection.notifier.CollectionChanged -= _on_collection_changed;
|
||||
return _collections.Count < num_before;
|
||||
}
|
||||
|
||||
@@ -157,43 +174,47 @@ namespace mbts.ui.controls {
|
||||
if (index == _collections.Count)
|
||||
Add(collection);
|
||||
else {
|
||||
_collections.Insert(index, collection);
|
||||
_on_collection_changed(collection, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, collection, 0));
|
||||
collection.notifier.CollectionChanged += _on_collection_changed;
|
||||
var wrapper = new NotifierCollectionWrapper(collection);
|
||||
_collections.Insert(index, wrapper);
|
||||
_on_collection_changed(wrapper, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, wrapper, 0));
|
||||
wrapper.CollectionChanged += _on_collection_changed;
|
||||
}
|
||||
}
|
||||
|
||||
public void RemoveAt(int index) {
|
||||
_check_disposed();
|
||||
Remove(_collections[index]);
|
||||
_remove(_collections[index]);
|
||||
}
|
||||
|
||||
public void Clear() {
|
||||
_check_disposed();
|
||||
foreach (var c in _collections)
|
||||
c.notifier.CollectionChanged -= _on_collection_changed;
|
||||
c.CollectionChanged -= _on_collection_changed;
|
||||
_collections.Clear();
|
||||
_target.Clear();
|
||||
}
|
||||
|
||||
public bool Contains((IReadOnlyCollection<T> items, INotifyCollectionChanged notifier) item) =>
|
||||
_collections.Contains(item);
|
||||
_collection_tuples.Contains(item);
|
||||
|
||||
public void CopyTo((IReadOnlyCollection<T> items, INotifyCollectionChanged notifier)[] array, int arrayIndex) =>
|
||||
_collections.CopyTo(array, arrayIndex);
|
||||
_collection_tuples.CopyTo(array, arrayIndex);
|
||||
|
||||
public int IndexOf((IReadOnlyCollection<T> items, INotifyCollectionChanged notifier) item) =>
|
||||
_collections.IndexOf(item);
|
||||
_collection_tuples.IndexOf(item);
|
||||
|
||||
public void Dispose() {
|
||||
if (!_is_disposed) {
|
||||
foreach (var c in _collections)
|
||||
c.notifier.CollectionChanged -= _on_collection_changed;
|
||||
c.CollectionChanged -= _on_collection_changed;
|
||||
_is_disposed = true;
|
||||
}
|
||||
}
|
||||
|
||||
public IEnumerator<(IReadOnlyCollection<T> items, INotifyCollectionChanged notifier)> GetEnumerator() => _collections.GetEnumerator();
|
||||
public IEnumerator<(IReadOnlyCollection<T> items, INotifyCollectionChanged notifier)> GetEnumerator() {
|
||||
for (int i = 0; i < _collections.Count; i++)
|
||||
yield return _collections[i].tuple;
|
||||
}
|
||||
IEnumerator IEnumerable.GetEnumerator() => _collections.GetEnumerator();
|
||||
|
||||
void _on_collection_changed(object sender, NotifyCollectionChangedEventArgs e) {
|
||||
@@ -208,40 +229,96 @@ namespace mbts.ui.controls {
|
||||
_target.InsertRange(e.NewStartingIndex + offset, e.NewItems);
|
||||
break;
|
||||
case NotifyCollectionChangedAction.Reset:
|
||||
var c = (ObservableCollection<T>)sender;
|
||||
_remove_collection_items((c, c), out int starting_index, out int _);
|
||||
_add_items(c, starting_index);
|
||||
var wrapper = sender as NotifierCollectionWrapper;
|
||||
_remove_collection_items(wrapper, out int starting_index, out int _);
|
||||
_add_items(wrapper.collection, starting_index);
|
||||
foreach (T item in wrapper)
|
||||
_target.Insert(starting_index++, item);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void _add_items(IList items, int index) {
|
||||
void _add_items(IEnumerable items, int index) {
|
||||
foreach (T item in items)
|
||||
_target.Insert(index++, item);
|
||||
}
|
||||
|
||||
void _remove_collection_items((IReadOnlyCollection<T> items, INotifyCollectionChanged notifier) collection, out int starting_index, out int collection_index) {
|
||||
void _remove_collection_items(NotifierCollectionWrapper wrapper, out int starting_index, out int collection_index, bool disconnect = false) =>
|
||||
_remove_collection_items(wrapper.tuple, out starting_index, out collection_index, disconnect);
|
||||
|
||||
void _remove_collection_items((IReadOnlyCollection<T> items, INotifyCollectionChanged notifier) collection, out int starting_index, out int collection_index, bool disconnect = false) {
|
||||
int ex_count = 0;
|
||||
int i = 0;
|
||||
for (; i < _collections.Count; i++) {
|
||||
if (_collections[i].items == collection.items)
|
||||
if (_collections[i].collection == collection.items) {
|
||||
if (disconnect)
|
||||
_collections[i].CollectionChanged -= _on_collection_changed;
|
||||
break;
|
||||
else ex_count += _collections[i].items.Count;
|
||||
} else ex_count += _collections[i].Count;
|
||||
}
|
||||
starting_index = ex_count;
|
||||
collection_index = i;
|
||||
for (++i; i < _collections.Count; i++)
|
||||
ex_count += _collections[i].items.Count;
|
||||
ex_count += _collections[i].Count;
|
||||
_target.RemoveRange(starting_index, _target.Count - ex_count);
|
||||
}
|
||||
|
||||
int _get_collection_start(object sender) =>
|
||||
_collections.TakeWhile(c => c.items != sender).Sum(c => c.items.Count);
|
||||
_collections.TakeWhile(c => c != sender).Sum(c => c.Count);
|
||||
|
||||
void _check_disposed() {
|
||||
if (_is_disposed)
|
||||
throw new ObjectDisposedException("ObservableCollectionAggregator");
|
||||
}
|
||||
|
||||
class NotifierCollectionWrapper : IReadOnlyCollection<T>, IList, INotifyCollectionChanged {
|
||||
public NotifierCollectionWrapper((IReadOnlyCollection<T> collection, INotifyCollectionChanged notifier) tuple)
|
||||
: this(tuple.collection, tuple.notifier) { }
|
||||
|
||||
public NotifierCollectionWrapper(IReadOnlyCollection<T> collection, INotifyCollectionChanged notifier) {
|
||||
this.collection = collection;
|
||||
this.notifier = notifier;
|
||||
notifier.CollectionChanged += _on_collection_changed;
|
||||
}
|
||||
|
||||
public object this[int index] {
|
||||
get => throw new NotImplementedException();
|
||||
set => throw new NotSupportedException();
|
||||
}
|
||||
|
||||
public IReadOnlyCollection<T> collection { get; }
|
||||
public INotifyCollectionChanged notifier { get; }
|
||||
|
||||
public (IReadOnlyCollection<T> collection, INotifyCollectionChanged notifier) tuple => (collection, notifier);
|
||||
|
||||
public int Count => collection.Count;
|
||||
|
||||
public bool IsReadOnly => true;
|
||||
public bool IsFixedSize => false;
|
||||
|
||||
public object SyncRoot => null;
|
||||
|
||||
public bool IsSynchronized => false;
|
||||
|
||||
public event NotifyCollectionChangedEventHandler CollectionChanged;
|
||||
|
||||
public int Add(object value) => throw new NotSupportedException();
|
||||
public void Clear() => throw new NotSupportedException();
|
||||
public bool Contains(object value) => collection.Contains((T)value);
|
||||
public void CopyTo(Array array, int index) {
|
||||
foreach (var v in collection)
|
||||
array.SetValue(v, index++);
|
||||
}
|
||||
public IEnumerator<T> GetEnumerator() => collection.GetEnumerator();
|
||||
public int IndexOf(object value) => collection.indexOf((T)value);
|
||||
public void Insert(int index, object value) => throw new NotSupportedException();
|
||||
public void Remove(object value) => throw new NotSupportedException();
|
||||
public void RemoveAt(int index) => throw new NotSupportedException();
|
||||
IEnumerator IEnumerable.GetEnumerator() => collection.GetEnumerator();
|
||||
|
||||
void _on_collection_changed(object sender, NotifyCollectionChangedEventArgs e) =>
|
||||
CollectionChanged?.Invoke(this, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ namespace mbts.ui {
|
||||
if (style == null) {
|
||||
if (!allow_null)
|
||||
throw new ArgumentException(nameof(style));
|
||||
} else if (style.TargetType != expected)
|
||||
} else if (!style.TargetType.IsAssignableFrom(expected))
|
||||
throw WPFExceptionBuilder.style_target_type(expected, style.TargetType);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,65 +0,0 @@
|
||||
using System;
|
||||
|
||||
namespace mbts.ui.utility.text {
|
||||
/// <summary>
|
||||
/// Contains a font family, size, and foreground (uint).
|
||||
/// <para/> NOTE: does not account for font weight, or typeface.
|
||||
/// </summary>
|
||||
struct FontParams : IEquatable<FontParams> {
|
||||
public readonly FontFamily font_family;
|
||||
public readonly double font_size;
|
||||
public readonly uint foreground;
|
||||
|
||||
public FontParams(FontFamily font_family, double font_size, uint foreground) {
|
||||
this.font_family = font_family;
|
||||
this.font_size = font_size;
|
||||
this.foreground = foreground;
|
||||
}
|
||||
|
||||
public bool Equals(FontParams other) =>
|
||||
other.font_family == font_family &&
|
||||
other.font_size == font_size &&
|
||||
other.foreground == foreground;
|
||||
|
||||
public override bool Equals(object obj) =>
|
||||
obj is FontParams fsk && Equals(fsk);
|
||||
|
||||
public override int GetHashCode() =>
|
||||
font_family.GetHashCode() ^
|
||||
font_size.GetHashCode() ^
|
||||
foreground.GetHashCode();
|
||||
|
||||
public static bool operator ==(FontParams x, FontParams y) =>
|
||||
x.Equals(y);
|
||||
|
||||
public static bool operator !=(FontParams x, FontParams y) =>
|
||||
!x.Equals(y);
|
||||
}
|
||||
|
||||
struct FontParamsKey<T> where T : struct, IEquatable<T> {
|
||||
public readonly T key;
|
||||
public readonly FontParams font_params;
|
||||
|
||||
public FontParamsKey(T key, FontParams font_params) {
|
||||
this.key = key;
|
||||
this.font_params = font_params;
|
||||
}
|
||||
|
||||
public bool Equals(FontParamsKey<T> other) =>
|
||||
other.key.Equals(key) &&
|
||||
other.font_params == font_params;
|
||||
|
||||
public override bool Equals(object obj) =>
|
||||
obj is FontParamsKey<T> fpk && Equals(fpk);
|
||||
|
||||
public override int GetHashCode() =>
|
||||
key.GetHashCode() ^
|
||||
font_params.GetHashCode();
|
||||
|
||||
public static bool operator ==(FontParamsKey<T> x, FontParamsKey<T> y) =>
|
||||
x.Equals(y);
|
||||
|
||||
public static bool operator !=(FontParamsKey<T> x, FontParamsKey<T> y) =>
|
||||
!x.Equals(y);
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,12 @@
|
||||
|
||||
namespace mbts {
|
||||
public static class DoubleHelper {
|
||||
public static bool AnyNaN(params double[] values) {
|
||||
for (int i = 0; i < values.Length; i++)
|
||||
if (double.IsNaN(values[i]))
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
public static bool IsNaNOrInfinity(double d) => double.IsNaN(d) || double.IsInfinity(d);
|
||||
public static bool IsNaNOrInfinityOr(double d, params double[] values) => IsNaNOrInfinity(d) || values.Contains(d);
|
||||
public static bool IsOneOf(double d, params double[] values) => values.Contains(d);
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using bts.shell.pnl;
|
||||
using bts.shell.orderbook;
|
||||
using bts.shell.pnl;
|
||||
using bts.utils;
|
||||
using mbts.ui.settings;
|
||||
using System;
|
||||
@@ -15,6 +16,7 @@ namespace bts.settings {
|
||||
// determine the various windows to which such rules apply.
|
||||
interface IFillsGridSettingsMigrationRule : ISettingsMigrationRule<GridSettings> { }
|
||||
interface IMarketViewGridSettingsMigrationRule : ISettingsMigrationRule<GridSettings> { }
|
||||
interface IOrderBookGridSettingsMigrationRule : ISettingsMigrationRule<OrderBookGridSettings> { }
|
||||
interface IPAndLGridSettingsMigrationRule : ISettingsMigrationRule<GridSettings> { }
|
||||
interface IPNLSettingsMigrationRule : ISettingsMigrationRule<PNLSettings> { }
|
||||
interface ISyntheticManagementMigrationRule : ISettingsMigrationRule<GridSettings> { }
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using bts;
|
||||
using bts.settings;
|
||||
using bts.shell.orderbook;
|
||||
using bts.shell.pnl;
|
||||
using bts.utils;
|
||||
using mbts.spark;
|
||||
@@ -28,6 +29,9 @@ namespace mbts.persist {
|
||||
_pandl_propi = ImmutableProperty.create<UserSettings, GridSettings>(us => us.pnl_grid, (us, gs) => us.with(provided_pnl_gridsettings: gs)),
|
||||
_synth_propi = ImmutableProperty.create<UserSettings, GridSettings>(us => us.synthetic_management_grid, (us, gs) => us.with(provided_synthetic_management_gridsettings: gs));
|
||||
|
||||
static readonly IImmutableProperty<UserSettings, OrderBookGridSettings>
|
||||
_orderbook_propi = ImmutableProperty.create<UserSettings, OrderBookGridSettings>(us => us.order_book_grid, (us, gs) => us.with(order_book_grid: gs));
|
||||
|
||||
static readonly IImmutableProperty<UserSettings, PNLSettings>
|
||||
_pnl_propi = ImmutableProperty.create<UserSettings, PNLSettings>(us => us.pnl, (us, pnl) => us.with(pnl: pnl));
|
||||
|
||||
@@ -35,6 +39,7 @@ namespace mbts.persist {
|
||||
readonly BehaviorSubject<UserSettings> _default_settings_subject = new BehaviorSubject<UserSettings>(UserSettings.factory_defaults);
|
||||
readonly IEnumerable<IFillsGridSettingsMigrationRule> _fills_migration_rules;
|
||||
readonly IEnumerable<IMarketViewGridSettingsMigrationRule> _mview_migration_rules;
|
||||
readonly IEnumerable<IOrderBookGridSettingsMigrationRule> _orderbook_migration_rules;
|
||||
readonly IEnumerable<IPAndLGridSettingsMigrationRule> _pandl_migration_rules;
|
||||
readonly IEnumerable<IPNLSettingsMigrationRule> _pnl_migration_rules;
|
||||
readonly IEnumerable<ISyntheticManagementMigrationRule> _synth_migration_rules;
|
||||
@@ -47,6 +52,7 @@ namespace mbts.persist {
|
||||
IUserPersist persist,
|
||||
[ImportMany]IEnumerable<IFillsGridSettingsMigrationRule> fills_migration_rules,
|
||||
[ImportMany]IEnumerable<IMarketViewGridSettingsMigrationRule> mview_migration_rules,
|
||||
[ImportMany]IEnumerable<IOrderBookGridSettingsMigrationRule> orderbook_migration_rules,
|
||||
[ImportMany]IEnumerable<IPAndLGridSettingsMigrationRule> pandl_migration_rules,
|
||||
[ImportMany]IEnumerable<IPNLSettingsMigrationRule> pnl_migration_rules,
|
||||
[ImportMany]IEnumerable<ISyntheticManagementMigrationRule> synth_migration_rules) {
|
||||
@@ -54,6 +60,7 @@ namespace mbts.persist {
|
||||
_persist = persist;
|
||||
_fills_migration_rules = fills_migration_rules ?? Enumerable.Empty<IFillsGridSettingsMigrationRule>();
|
||||
_mview_migration_rules = mview_migration_rules ?? Enumerable.Empty<IMarketViewGridSettingsMigrationRule>();
|
||||
_orderbook_migration_rules = orderbook_migration_rules ?? Enumerable.Empty<IOrderBookGridSettingsMigrationRule>();
|
||||
_pandl_migration_rules = pandl_migration_rules ?? Enumerable.Empty<IPAndLGridSettingsMigrationRule>();
|
||||
_pnl_migration_rules = pnl_migration_rules ?? Enumerable.Empty<IPNLSettingsMigrationRule>();
|
||||
_synth_migration_rules = synth_migration_rules ?? Enumerable.Empty<ISyntheticManagementMigrationRule>();
|
||||
@@ -130,6 +137,12 @@ namespace mbts.persist {
|
||||
_mview_migration_rules,
|
||||
_mview_propi);
|
||||
|
||||
public IWindowSettingsManager<OrderBookGridSettings> create_orderbook_settings_manager(OrderBookGridSettings instance_settings) =>
|
||||
_create_settings_manager(
|
||||
instance_settings,
|
||||
_orderbook_migration_rules,
|
||||
_orderbook_propi);
|
||||
|
||||
public IWindowSettingsManager<GridSettings> create_pandl_settings_manager(GridSettings instance_settings) =>
|
||||
_create_settings_manager(
|
||||
instance_settings,
|
||||
|
||||
@@ -56,6 +56,9 @@ namespace bts {
|
||||
|
||||
public static T infer<T, A, B>(object value, Func<A, T> a, Func<B, T> b) =>
|
||||
OneOf<A, B>.infer(value).Match(a, b);
|
||||
|
||||
public static T infer<T, A, B, C>(object value, Func<A, T> a, Func<B, T> b, Func<C, T> c) =>
|
||||
OneOf<A, B, C>.infer(value).Match(a, b, c);
|
||||
}
|
||||
|
||||
public class OneOf<A, B> {
|
||||
|
||||
@@ -1,20 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace mbts.utils {
|
||||
public class ReverseComparer<T> : IComparer<T> {
|
||||
private readonly IComparer<T> _wrapped;
|
||||
|
||||
public ReverseComparer(IComparer<T> wrapped) {
|
||||
_wrapped = wrapped;
|
||||
}
|
||||
|
||||
public int Compare(T x, T y) {
|
||||
// reverse operands
|
||||
return _wrapped.Compare(y, x);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -23,6 +23,11 @@ namespace mbts.ui.shell {
|
||||
void clear_error(string property);
|
||||
}
|
||||
|
||||
interface IReadOnlyProperty<T> {
|
||||
event PropertyChange<T> Changing, Changed;
|
||||
T value { get; }
|
||||
}
|
||||
|
||||
interface IProperty<T> : IObservable<(T oldval, T newval)> {
|
||||
event PropertyChange<T> Changing, Changed;
|
||||
T Value { get; set; }
|
||||
@@ -36,11 +41,32 @@ namespace mbts.ui.shell {
|
||||
public delegate void PropertyChange<T>(T oldval, T newval);
|
||||
|
||||
static class ValidationViewModelExtensions {
|
||||
public static IDisposable echo<T>(this IRaiseNotifyPropertyChanged vm, IProperty<T> source_property, string target_property_name) {
|
||||
source_property.Changed += on_changed;
|
||||
return Disposable.Create(() => source_property.Changed -= on_changed);
|
||||
void on_changed(T oldval, T newval) =>
|
||||
vm.raise_notify_property_changed(target_property_name);
|
||||
}
|
||||
|
||||
public static void assert(this IValidationViewModel vm, bool condition, string property, string message) {
|
||||
if (condition) vm.clear_error(property);
|
||||
else vm.set_error(property, message);
|
||||
}
|
||||
|
||||
public static IDisposable subscribe_changed<T>(this IProperty<T> property, IObserver<(T oldval, T newval)> observer) {
|
||||
property.Changed += on_changed;
|
||||
return Disposable.Create(() => property.Changed -= on_changed);
|
||||
void on_changed(T oldval, T newval) =>
|
||||
observer.OnNext((oldval, newval));
|
||||
}
|
||||
|
||||
public static IDisposable subscribe_changing<T>(this IProperty<T> property, IObserver<(T oldval, T newval)> observer) {
|
||||
property.Changing += on_changing;
|
||||
return Disposable.Create(() => property.Changing -= on_changing);
|
||||
void on_changing(T oldval, T newval) =>
|
||||
observer.OnNext((oldval, newval));
|
||||
}
|
||||
|
||||
public static IObservable<bool> subscribe_has_errors(this INotifyDataErrorInfo vm) =>
|
||||
Observable.Create<bool>(o => {
|
||||
vm.ErrorsChanged += on_errors_changed;
|
||||
@@ -53,6 +79,17 @@ namespace mbts.ui.shell {
|
||||
// ensure notifications will be in on the same thread as IProperty<T>.Subscribe (don't use .Select extension)
|
||||
Observable.Create<T>(o => property.Subscribe(ch => o.OnNext(ch.newval)));
|
||||
|
||||
public static IObservable<T> SubscribeValue<T>(this IProperty<T> property, bool start_immediately) =>
|
||||
start_immediately ?
|
||||
// ensure notifications will be in on the same thread as IProperty<T>.Subscribe (don't use .Select extension)
|
||||
Observable.Create<T>(o => {
|
||||
o.OnNext(property.Value);
|
||||
return property.Subscribe(ch => o.OnNext(ch.newval));
|
||||
}) :
|
||||
SubscribeValue(property);
|
||||
|
||||
//public static IDisposable S
|
||||
|
||||
public static IProperty<T> block_reentrancy<T>(this IProperty<T> source) =>
|
||||
new GatedPropertyWrapper<T>(source);
|
||||
|
||||
@@ -89,6 +126,7 @@ namespace mbts.ui.shell {
|
||||
return GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance);
|
||||
});
|
||||
}
|
||||
|
||||
protected void RaisePropertyChanged([CallerMemberName]string property="") {
|
||||
if (PropertyChanged != null) {
|
||||
PropertyChanged(this, new PropertyChangedEventArgs(property));
|
||||
|
||||
@@ -22,12 +22,20 @@ namespace bts.proto {
|
||||
}
|
||||
|
||||
public Color color => _color.rgb_color();
|
||||
public Color argb_color => Color.FromArgb((byte)(MathUtils.within_range(0, 1, _opacity) * 255.0), color.R, color.G, color.B);
|
||||
// TODO: get rid of this nonsense, all we need is a uint to store color & opacity
|
||||
// (opacity is just the alpha bits of the color)
|
||||
public Color argb_color =>
|
||||
opacity == 1 ?
|
||||
_color.argb_color() :
|
||||
Color.FromArgb((byte)(MathUtils.within_range(0, 1, _opacity) * 255.0), color.R, color.G, color.B);
|
||||
public double opacity => _opacity;
|
||||
|
||||
public ColorOpacityProto with(uint? color = null, double? opacity = null) =>
|
||||
new ColorOpacityProto(color ?? _color, opacity ?? _opacity);
|
||||
|
||||
public SolidColorBrush to_argb_brush() =>
|
||||
new SolidColorBrush(argb_color) { Opacity = _opacity };
|
||||
|
||||
public SolidColorBrush to_brush() =>
|
||||
new SolidColorBrush(_color.rgb_color()) { Opacity = _opacity };
|
||||
|
||||
|
||||
@@ -5,9 +5,23 @@
|
||||
xmlns:ui="clr-namespace:mbts.ui"
|
||||
>
|
||||
<!--Control Base Colors-->
|
||||
<Color x:Key="Control.SuperLightGrayColor">#F6F6F6</Color>
|
||||
<Color x:Key="Control.LightGrayColor">#E5E5E5</Color>
|
||||
<Color x:Key="Control.LightMediumGrayColor">#CFCFCF</Color>
|
||||
<Color x:Key="Control.MediumGrayColor">#B3B3B3</Color>
|
||||
<Color x:Key="Control.MediumDarkGrayColor">#ACACAC</Color>
|
||||
<Color x:Key="Control.AlphaLightGrayColor">#10000000</Color>
|
||||
<Color x:Key="Control.AlphaLightMediumGrayColor">#24000000</Color>
|
||||
<Color x:Key="Control.AlphaMediumGrayColor">#3A000000</Color>
|
||||
<Color x:Key="Control.AlphaMediumGrayColor2">#FF0000</Color>
|
||||
<Color x:Key="Control.DarkMediumGrayColor">#FF959595</Color>
|
||||
<Color x:Key="Control.DarkGrayColor">#707070</Color>
|
||||
<Color x:Key="Text.Prompt.Foreground.Color">#FF999999</Color>
|
||||
|
||||
<SolidColorBrush x:Key="Control.AlphaLightGrayBrush" Color="{StaticResource Control.AlphaLightGrayColor}"/>
|
||||
<SolidColorBrush x:Key="Control.AlphaLightMediumGrayBrush" Color="{StaticResource Control.AlphaLightMediumGrayColor}"/>
|
||||
<SolidColorBrush x:Key="Control.LightGrayBrush" Color="{StaticResource Control.LightGrayColor}"/>
|
||||
<SolidColorBrush x:Key="Control.LightMediumGrayBrush" Color="{StaticResource Control.LightMediumGrayColor}"/>
|
||||
<SolidColorBrush x:Key="Control.MediumGrayBrush" Color="{StaticResource Control.MediumGrayColor}"/>
|
||||
<SolidColorBrush x:Key="CommonBackground" Color="{ui:BTSColor common_background}" />
|
||||
<SolidColorBrush x:Key="ControlBackground" Color="{x:Static mbts:BTSColors.common_background}" />
|
||||
@@ -130,4 +144,4 @@
|
||||
<SolidColorBrush x:Key="ListBoxItem.Selected.Inactive.Border" Color="{StaticResource ListBoxItem.Selected.Inactive.Border.Color}"/>
|
||||
<SolidColorBrush x:Key="ListBoxItem.MouseOver.Background" Color="{StaticResource ListBoxItem.MouseOver.Background.Color}"/>
|
||||
<SolidColorBrush x:Key="ListBoxItem.MouseOver.Border" Color="{StaticResource ListBoxItem.MouseOver.Border.Color}"/>
|
||||
</ResourceDictionary>
|
||||
</ResourceDictionary>
|
||||
@@ -1,78 +1,102 @@
|
||||
<c:PopupControl
|
||||
x:Class="mbts.ui.controls.ActionMenu"
|
||||
<ResourceDictionary
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:u="clr-namespace:mbts.ui"
|
||||
xmlns:c="clr-namespace:mbts.ui.controls"
|
||||
Margin="1"
|
||||
Background="Transparent"
|
||||
BorderBrush="Transparent"
|
||||
Padding="2"
|
||||
PopupPlacement="Bottom"
|
||||
ShowExpanderArrow="False"
|
||||
Cursor="Hand"
|
||||
xmlns:cv="clr-namespace:mbts.ui.converters"
|
||||
>
|
||||
<c:PopupControl.Resources>
|
||||
<CollectionViewSource x:Key="ItemsSourceView" Source="{Binding ItemsSource, RelativeSource={RelativeSource AncestorType=c:ActionMenu}}">
|
||||
<CollectionViewSource.GroupDescriptions>
|
||||
<PropertyGroupDescription PropertyName="group"/>
|
||||
</CollectionViewSource.GroupDescriptions>
|
||||
</CollectionViewSource>
|
||||
</c:PopupControl.Resources>
|
||||
<c:PopupControl.Header>
|
||||
<Border Width="16">
|
||||
<SolidColorBrush x:Key="SeparatorFill" Color="{DynamicResource Control.AlphaMediumGrayColor}"/>
|
||||
|
||||
<ControlTemplate x:Key="ActionMenuTemplate" TargetType="c:ActionMenu">
|
||||
<Border
|
||||
HorizontalAlignment="{TemplateBinding HorizontalAlignment}"
|
||||
VerticalAlignment="{TemplateBinding VerticalAlignment}"
|
||||
Width="{TemplateBinding Width}"
|
||||
Height="{TemplateBinding Height}"
|
||||
>
|
||||
<u:StateManager.Groups>
|
||||
<u:StateGroupsManager AttachToMembers="True">
|
||||
<u:StateDefinitionCollection>
|
||||
<u:MouseStateDefinition MouseSource="{Binding Group.Target, RelativeSource={RelativeSource Self}}"/>
|
||||
</u:StateDefinitionCollection>
|
||||
</u:StateGroupsManager>
|
||||
</u:StateManager.Groups>
|
||||
<VisualStateManager.VisualStateGroups>
|
||||
<VisualStateGroup Name="MouseStates">
|
||||
<VisualState Name="Normal"/>
|
||||
<VisualState Name="MouseOver" Storyboard="{DynamicResource Button.MouseOver.Storyboard}"/>
|
||||
</VisualStateGroup>
|
||||
</VisualStateManager.VisualStateGroups>
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition/>
|
||||
<RowDefinition/>
|
||||
<RowDefinition/>
|
||||
</Grid.RowDefinitions>
|
||||
<Rectangle Grid.Row="0" Fill="Black" Height="2" HorizontalAlignment="Stretch"/>
|
||||
<Rectangle Grid.Row="1" Fill="Black" Height="2" Margin="0,4,0,0" HorizontalAlignment="Stretch"/>
|
||||
<Rectangle Grid.Row="2" Fill="Black" Height="2" Margin="0,4,0,0" HorizontalAlignment="Stretch"/>
|
||||
<Button
|
||||
x:Name="button"
|
||||
Content="{TemplateBinding Header}"
|
||||
ContentTemplate="{TemplateBinding HeaderTemplate}"
|
||||
ContentTemplateSelector="{TemplateBinding HeaderTemplateSelector}"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Stretch"
|
||||
Padding="2.5,2"
|
||||
Style="{DynamicResource SparkToolBarButton}"
|
||||
>
|
||||
<Button.Background>
|
||||
<SolidColorBrush/>
|
||||
</Button.Background>
|
||||
</Button>
|
||||
<Popup c:PopupViewController.ButtonSource="{Binding ElementName=button}" Placement="Bottom" PlacementTarget="{Binding ElementName=button}" StaysOpen="False">
|
||||
<Border Background="{DynamicResource Control.LightGrayBrush}" BorderThickness="1">
|
||||
<Border.BorderBrush>
|
||||
<SolidColorBrush Color="{DynamicResource Control.DarkMediumGrayColor}"/>
|
||||
</Border.BorderBrush>
|
||||
<ItemsPresenter/>
|
||||
</Border>
|
||||
</Popup>
|
||||
</Grid>
|
||||
</Border>
|
||||
</c:PopupControl.Header>
|
||||
<Menu ItemsSource="{Binding Source={StaticResource ItemsSourceView}}">
|
||||
<Menu.ItemsPanel>
|
||||
<ItemsPanelTemplate>
|
||||
<StackPanel Orientation="Vertical"/>
|
||||
</ItemsPanelTemplate>
|
||||
</Menu.ItemsPanel>
|
||||
<Menu.GroupStyle>
|
||||
<GroupStyle>
|
||||
<GroupStyle.ContainerStyle>
|
||||
<Style TargetType="GroupItem">
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="GroupItem">
|
||||
<StackPanel>
|
||||
<ItemsPresenter/>
|
||||
<Rectangle HorizontalAlignment="Stretch" Height="1" Margin="22,0,0,0" Fill="LightGray"/>
|
||||
</StackPanel>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
</GroupStyle.ContainerStyle>
|
||||
</GroupStyle>
|
||||
</Menu.GroupStyle>
|
||||
<Menu.ItemContainerStyle>
|
||||
<Style TargetType="MenuItem" BasedOn="{StaticResource {x:Type MenuItem}}">
|
||||
<!--NOTE: CommandParameter binding must be declared BEFORE Command,
|
||||
otherwise, CanExecuted is called before the parameter value is realized...I know, it sucks...-->
|
||||
<Setter Property="CommandParameter" Value="{Binding parameter}"/>
|
||||
<Setter Property="Command" Value="{Binding command}"/>
|
||||
<Setter Property="Header" Value="{Binding name}"/>
|
||||
<Setter Property="HeaderTemplate">
|
||||
<Setter.Value>
|
||||
<DataTemplate>
|
||||
<TextBlock Text="{Binding}" VerticalAlignment="Center"/>
|
||||
</DataTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
<Setter Property="Icon" Value="{Binding icon, Converter={StaticResource IconConverter}}"/>
|
||||
<Setter Property="MinWidth" Value="167"/>
|
||||
</Style>
|
||||
</Menu.ItemContainerStyle>
|
||||
</Menu>
|
||||
</c:PopupControl>
|
||||
</ControlTemplate>
|
||||
|
||||
<DataTemplate x:Key="HamburgerButtonTemplate">
|
||||
<Image
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Stretch"
|
||||
Source="{x:Static u:Icons.HamburgerMenu}"
|
||||
/>
|
||||
</DataTemplate>
|
||||
|
||||
<Style x:Key="ActionMenuBase" TargetType="c:ActionMenu">
|
||||
<Setter Property="Background">
|
||||
<Setter.Value>
|
||||
<SolidColorBrush/>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
<Setter Property="BorderBrush">
|
||||
<Setter.Value>
|
||||
<SolidColorBrush/>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
<Setter Property="Template" Value="{StaticResource ActionMenuTemplate}"/>
|
||||
</Style>
|
||||
|
||||
<DataTemplate DataType="{x:Type u:MenuItemVM}">
|
||||
<TextBlock Text="{Binding}" VerticalAlignment="Center"/>
|
||||
</DataTemplate>
|
||||
|
||||
<Style TargetType="c:ActionMenu" BasedOn="{StaticResource ActionMenuBase}">
|
||||
<Setter Property="Panel.ZIndex" Value="1"/>
|
||||
<Setter Property="HorizontalAlignment" Value="Right"/>
|
||||
<Setter Property="VerticalAlignment" Value="Top"/>
|
||||
<Setter Property="Margin" Value="1"/>
|
||||
<Setter Property="HeaderTemplate" Value="{StaticResource HamburgerButtonTemplate}"/>
|
||||
<Setter Property="Width" Value="22"/>
|
||||
<Setter Property="ItemContainerStyle">
|
||||
<Setter.Value>
|
||||
<Style TargetType="MenuItem">
|
||||
<Setter Property="MinWidth" Value="167"/>
|
||||
<Setter Property="CommandParameter" Value="{Binding parameter}"/>
|
||||
<Setter Property="Command" Value="{Binding command}"/>
|
||||
<Setter Property="Header" Value="{Binding name}"/>
|
||||
<Setter Property="Icon" Value="{Binding icon, Converter={x:Static cv:ImageSourceIconConverter.Instance}}"/>
|
||||
</Style>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
</ResourceDictionary>
|
||||
@@ -1,22 +1,15 @@
|
||||
using System.Collections;
|
||||
using System.Linq;
|
||||
using System.Windows;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
|
||||
namespace mbts.ui.controls {
|
||||
/// <summary>
|
||||
/// Interaction logic for ActionMenu.xaml
|
||||
/// </summary>
|
||||
partial class ActionMenu : PopupControl {
|
||||
public ActionMenu() =>
|
||||
InitializeComponent();
|
||||
class ActionMenu : HeaderedItemsControl {
|
||||
static ActionMenu() =>
|
||||
DefaultStyleKeyProperty.OverrideMetadata(typeof(ActionMenu), new FrameworkPropertyMetadata(typeof(ActionMenu)));
|
||||
|
||||
public static readonly DependencyProperty ItemsSourceProperty =
|
||||
WPFHelper.create_dp<IEnumerable, ActionMenu>(
|
||||
nameof(ItemsSource), Enumerable.Empty<IMenuItemVM>());
|
||||
|
||||
public IEnumerable ItemsSource {
|
||||
get => GetValue(ItemsSourceProperty) as IEnumerable;
|
||||
set => SetValue(ItemsSourceProperty, value);
|
||||
}
|
||||
protected override DependencyObject GetContainerForItemOverride() =>
|
||||
new MenuItem();
|
||||
}
|
||||
}
|
||||
@@ -38,7 +38,7 @@ namespace mbts.ui.controls.adorners {
|
||||
move_to(e.GetPosition(target));
|
||||
|
||||
void move_to(Point pt) =>
|
||||
mta.RenderTransform = new TranslateTransform(pt.X, pt.Y);
|
||||
mta.Margin = new Thickness(pt.X, pt.Y, 0, 0);
|
||||
}
|
||||
|
||||
public static IDisposable trace_drag(FrameworkElement target, Visual trace_visual, Size trace_visual_size) {
|
||||
@@ -55,7 +55,7 @@ namespace mbts.ui.controls.adorners {
|
||||
move_to(e.GetPosition(target));
|
||||
|
||||
void move_to(Point pt) =>
|
||||
mta.RenderTransform = new TranslateTransform(pt.X, pt.Y);
|
||||
mta.Margin = new Thickness(pt.X, pt.Y, 0, 0);
|
||||
}
|
||||
|
||||
protected override Visual GetVisualChild(int index) =>
|
||||
|
||||
@@ -9,13 +9,14 @@
|
||||
<SolidColorBrush x:Key="Ellipse.Pressed.Brush" Color="#FF3C77DD"/>
|
||||
<SolidColorBrush x:Key="Ellipse.Disabled.Brush" Color="Gray"/>
|
||||
<Style TargetType="c:ExpanderToggleButton" BasedOn="{StaticResource {x:Type ToggleButton}}">
|
||||
<Setter Property="Background" Value="Transparent"/>
|
||||
<Setter Property="BorderBrush" Value="Black"/>
|
||||
<Setter Property="BorderThickness" Value="1"/>
|
||||
<Setter Property="Padding" Value="3,0,0,0"/>
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="ToggleButton">
|
||||
<Border>
|
||||
<ControlTemplate TargetType="c:ExpanderToggleButton">
|
||||
<Border Background="{TemplateBinding Background}">
|
||||
<Grid Background="Transparent">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
@@ -30,7 +31,15 @@
|
||||
Stroke="{StaticResource Path.Static.Brush}"
|
||||
StrokeThickness="2"
|
||||
VerticalAlignment="Center"
|
||||
/>
|
||||
>
|
||||
<Path.Visibility>
|
||||
<Binding Path="ShowExpanderArrow" RelativeSource="{RelativeSource TemplatedParent}">
|
||||
<Binding.Converter>
|
||||
<cv:BooleanToVisibilityConverter VisibilityForFalse="Hidden" VisibilityForTrue="Visible"/>
|
||||
</Binding.Converter>
|
||||
</Binding>
|
||||
</Path.Visibility>
|
||||
</Path>
|
||||
<ContentPresenter
|
||||
Grid.Column="1"
|
||||
ContentSource="{TemplateBinding Content}"
|
||||
|
||||
@@ -1,12 +1,28 @@
|
||||
using System.Windows;
|
||||
using System.Windows.Controls.Primitives;
|
||||
using System.Windows.Input;
|
||||
|
||||
namespace mbts.ui.controls {
|
||||
/// <summary>
|
||||
/// Interaction logic for ExpanderToggleButton.xaml
|
||||
/// </summary>
|
||||
class ExpanderToggleButton : ToggleButton {
|
||||
public static readonly RoutedCommand ToggleCommand = new RoutedCommand();
|
||||
public static readonly ExecutedRoutedEventHandler ToggleExecuted = (s, e) => {
|
||||
var bn = s as ToggleButton ?? e.Parameter as ToggleButton;
|
||||
if (bn != null)
|
||||
bn.SetCurrentValue(IsCheckedProperty, !bn.IsChecked.GetValueOrDefault());
|
||||
};
|
||||
|
||||
static ExpanderToggleButton() =>
|
||||
DefaultStyleKeyProperty.OverrideMetadata(typeof(ExpanderToggleButton), new FrameworkPropertyMetadata(typeof(ExpanderToggleButton)));
|
||||
|
||||
public static readonly DependencyProperty ShowExpanderArrowProperty =
|
||||
WPFHelper.create_dp<bool, ExpanderToggleButton>(nameof(ShowExpanderArrow), true);
|
||||
|
||||
public bool ShowExpanderArrow {
|
||||
get => (bool)GetValue(ShowExpanderArrowProperty);
|
||||
set => SetValue(ShowExpanderArrowProperty, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
using mbts.spark;
|
||||
using bts.collections;
|
||||
using mbts.spark;
|
||||
using mbts.spark.utilities;
|
||||
using ProtoBuf;
|
||||
using System.Collections.Generic;
|
||||
@@ -23,6 +24,7 @@ namespace mbts.ui.controls.color_picker {
|
||||
yield return factory_order_colors_palette(order_factory);
|
||||
yield return application_palette();
|
||||
yield return bts_palette();
|
||||
yield return system_palette();
|
||||
yield return blank_palette();
|
||||
}
|
||||
|
||||
@@ -109,6 +111,14 @@ namespace mbts.ui.controls.color_picker {
|
||||
}
|
||||
};
|
||||
|
||||
public static ColorPalette system_palette() =>
|
||||
new ColorPalette {
|
||||
group = "System",
|
||||
colors = ColorUtils.SystemColors.WrapReadOnlySelector(
|
||||
nc => new NamedColor(nc.value, nc.name))
|
||||
.ToList()
|
||||
};
|
||||
|
||||
public static ColorPalette blank_palette() =>
|
||||
new ColorPalette {
|
||||
group = "Custom",
|
||||
|
||||
@@ -1,11 +1,132 @@
|
||||
using System;
|
||||
using bts.collections;
|
||||
using bts.utils;
|
||||
using mbts.ui.utilities;
|
||||
using mbts.utils;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Runtime.InteropServices;
|
||||
using Color = System.Windows.Media.Color;
|
||||
using System.Windows.Media;
|
||||
|
||||
namespace mbts.ui.controls.color_picker {
|
||||
public static class ColorUtils {
|
||||
static class ColorUtils {
|
||||
// (L)ight -> (H)ue comparer (hierarchical)
|
||||
public static readonly IComparer<Color> LHComparer =
|
||||
new ComparerBuilder<Color> {
|
||||
ReverseComparer<double>.Default.wrap<double, Color>(brightness),
|
||||
Comparer<double>.Default.wrap<double, Color>(hue)
|
||||
}.to_comparer();
|
||||
|
||||
static readonly Named<Color>[]
|
||||
_system_colors,
|
||||
// 'pure' means no grays
|
||||
_pure_web_colors,
|
||||
_gray_web_colors,
|
||||
_all_web_colors,
|
||||
/// The 'grid' arrays below have the same colors as the arrays above, but are sorted differently to represent a grid structure:
|
||||
/// - 140 web colors are split into 7 rows (20 items each),
|
||||
/// - each row represents a 'hue' bucket
|
||||
/// - the items within each row are sorted by lightness
|
||||
/// Note: these 'grid-sorted' arrays serve as the [default]ItemsSource for the <see cref="TabularColorPicker"/>, which presents the items in a UniformGrid.
|
||||
_pure_web_colors_grid,
|
||||
_all_web_colors_grid;
|
||||
|
||||
static ColorUtils() {
|
||||
var named_color_lh_comp = LHComparer.wrap<Color, Named<Color>>(nc => nc.value);
|
||||
_system_colors = Array.ConvertAll(
|
||||
typeof(Colors).GetProperties(BindingFlags.Static | BindingFlags.Public),
|
||||
p => new Named<Color>(
|
||||
DisplayNameHelper.camel_to_title_case(p.Name),
|
||||
(Color)p.GetValue(null)));
|
||||
Array.Sort(_system_colors, named_color_lh_comp);
|
||||
_all_web_colors = Array.ConvertAll(
|
||||
typeof(System.Drawing.Color)
|
||||
.GetProperties(BindingFlags.Public | BindingFlags.Static),
|
||||
p => {
|
||||
var c = System.Drawing.Color.FromName(p.Name);
|
||||
return new Named<Color>(p.Name, Color.FromArgb(c.A, c.R, c.G, c.B));
|
||||
});
|
||||
Array.Sort(_all_web_colors, named_color_lh_comp);
|
||||
var all_web_colors_grid = new Named<Color>[_all_web_colors.Length - 1];
|
||||
int adj = 0;
|
||||
for (int i = 0; i < _all_web_colors.Length; i++) {
|
||||
if (_all_web_colors[i].value.A == 0) {
|
||||
adj++;
|
||||
continue;
|
||||
}
|
||||
all_web_colors_grid[i - adj] = _all_web_colors[i];
|
||||
}
|
||||
SortColorGrid(ref all_web_colors_grid, nc => nc.value);
|
||||
_all_web_colors_grid = all_web_colors_grid;
|
||||
// break up into pure/gray
|
||||
int num_gray = all_web_colors_grid.Count(nc => nc.value.is_gray());
|
||||
_pure_web_colors = new Named<Color>[all_web_colors_grid.Length - num_gray];
|
||||
_gray_web_colors = new Named<Color>[num_gray];
|
||||
int gpos = 0;
|
||||
int egpos = 0;
|
||||
for (int i = 0; i < all_web_colors_grid.Length; i++) {
|
||||
var nc = all_web_colors_grid[i];
|
||||
if (nc.value.is_gray()) {
|
||||
_gray_web_colors[gpos++] = nc;
|
||||
} else {
|
||||
_pure_web_colors[egpos++] = nc;
|
||||
}
|
||||
}
|
||||
// gray
|
||||
Array.Sort(_gray_web_colors, Comparer<double>.Default.wrap<double, Named<Color>>(nc => nc.value.brightness()));
|
||||
// pure
|
||||
var pure_web_colors_grid = _pure_web_colors.ToArray();
|
||||
SortColorGrid(ref pure_web_colors_grid, nc => nc.value);
|
||||
_pure_web_colors_grid = pure_web_colors_grid;
|
||||
}
|
||||
|
||||
public static IReadOnlyList<Named<Color>> SystemColors =>
|
||||
_system_colors;
|
||||
|
||||
public static IReadOnlyList<NamedColorVM> SystemColorVMs =>
|
||||
_system_colors.WrapSelectorRO(_mk_readonly_vm);
|
||||
|
||||
public static IReadOnlyList<Named<Color>> WebColors =>
|
||||
_all_web_colors;
|
||||
|
||||
public static IReadOnlyList<NamedColorVM> WebColorVMs =>
|
||||
_all_web_colors.WrapSelectorRO(_mk_readonly_vm);
|
||||
|
||||
public static IReadOnlyList<NamedColorVM> GrayWebColorVMs =>
|
||||
_gray_web_colors.WrapSelectorRO(_mk_readonly_vm);
|
||||
|
||||
public static IReadOnlyList<NamedColorVM> PureWebColorVMs =>
|
||||
_pure_web_colors_grid.WrapSelectorRO(_mk_readonly_vm);
|
||||
|
||||
public static IReadOnlyList<NamedColorVM> WebColorVMGrid =>
|
||||
_all_web_colors_grid.WrapSelectorRO(_mk_readonly_vm);
|
||||
|
||||
public static void SortColorGrid<T>(ref T[] color_items, Func<T, Color> get_color, int column_count = 20) {
|
||||
// Sorting colors is a lot harder than one would think, esp when the distribution of rgb values is non-linear.
|
||||
// The questions one must ask are "how many shade groups should there be, and futhermore, how is a shade defined?"
|
||||
// The logic employed below first breaks the colors up into 6 groups of hues (each occupying a range of 60/360 degrees of hue).
|
||||
// A somewhat aribitrary offset of '10' is applied to achieve 'better looking' hue groups.
|
||||
// -------------------------------------------------------------------
|
||||
/// 1) sort by hue buckets (divide the 360 degrees of hue into 6 slices)
|
||||
var hue_comp = Comparer<int>.Default.wrap<int, T>(ci => {
|
||||
double raw_hue = get_color(ci).hue();
|
||||
double adj_hue = raw_hue + 10;
|
||||
if (adj_hue > 360) {
|
||||
adj_hue -= 360;
|
||||
}
|
||||
int hue_bucket = (int)Math.Ceiling(adj_hue / 60.0);
|
||||
return hue_bucket;
|
||||
});
|
||||
Array.Sort(color_items, hue_comp);
|
||||
/// 2) break up the array into rows, and sort each row by lightness
|
||||
var lightness_comp = Comparer<double>.Default.wrap<double, T>(ci => get_color(ci).brightness()).reverse();
|
||||
for (int i = 0; i < color_items.Length; i+=column_count) {
|
||||
int row_size = Math.Min(column_count, color_items.Length - i);
|
||||
Array.Sort(color_items, i, row_size, lightness_comp);
|
||||
}
|
||||
}
|
||||
|
||||
[DllImport("shlwapi.dll")]
|
||||
public static extern int ColorHLSToRGB(int H, int L, int S);
|
||||
|
||||
@@ -15,7 +136,6 @@ namespace mbts.ui.controls.color_picker {
|
||||
return Color.FromArgb(255, bytes[0], bytes[1], bytes[2]);
|
||||
}
|
||||
|
||||
|
||||
public static string ToHexString(this Color c) =>
|
||||
"#" + c.R.ToString("X2") + c.G.ToString("X2") + c.B.ToString("X2");
|
||||
|
||||
@@ -170,17 +290,7 @@ namespace mbts.ui.controls.color_picker {
|
||||
return Color.FromRgb((byte)(from.R + rdiff), (byte)(from.G + gdiff), (byte)(from.B + bdiff));
|
||||
}
|
||||
|
||||
// https://stackoverflow.com/questions/5091455/web-color-list-in-c-sharp-application
|
||||
public static List<Color> GetWebColors() {
|
||||
Type colors = typeof(System.Drawing.Color);
|
||||
PropertyInfo[] colorInfo = colors.GetProperties(BindingFlags.Public |
|
||||
BindingFlags.Static);
|
||||
List<Color> list = new List<Color>();
|
||||
foreach (PropertyInfo info in colorInfo) {
|
||||
var c = System.Drawing.Color.FromName(info.Name);
|
||||
list.Add(Color.FromArgb(c.A, c.R, c.G, c.B));
|
||||
}
|
||||
return list;
|
||||
}
|
||||
static NamedColorVM _mk_readonly_vm(Named<Color> nc) =>
|
||||
new NamedColorVM(new NamedColor(nc.value, nc.name), false);
|
||||
}
|
||||
}
|
||||
@@ -5,6 +5,7 @@
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:u="clr-namespace:mbts.ui"
|
||||
xmlns:c="clr-namespace:mbts.ui.controls"
|
||||
xmlns:cv="clr-namespace:mbts.ui.converters"
|
||||
xmlns:cp="clr-namespace:mbts.ui.controls.color_picker"
|
||||
>
|
||||
<Control.Resources>
|
||||
@@ -74,7 +75,29 @@
|
||||
</Control.Resources>
|
||||
<Control.Template>
|
||||
<ControlTemplate TargetType="cp:ColorPickerControl">
|
||||
<Grid>
|
||||
<Grid DataContext="{Binding Model, RelativeSource={RelativeSource TemplatedParent}}">
|
||||
<u:StateManager.VisualState>
|
||||
<Binding Path="ShowControls" RelativeSource="{RelativeSource TemplatedParent}">
|
||||
<Binding.Converter>
|
||||
<cv:BooleanToObjectConverter ValueForFalse="HideControls" ValueForTrue="ShowControls"/>
|
||||
</Binding.Converter>
|
||||
</Binding>
|
||||
</u:StateManager.VisualState>
|
||||
<VisualStateManager.VisualStateGroups>
|
||||
<VisualStateGroup Name="ControlStates">
|
||||
<VisualState Name="ShowControls"/>
|
||||
<VisualState Name="HideControls">
|
||||
<Storyboard>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="PaletteTabsGrid" Storyboard.TargetProperty="Visibility">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="{x:Static Visibility.Collapsed}"/>
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="SlidersGrid" Storyboard.TargetProperty="Visibility">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="{x:Static Visibility.Collapsed}"/>
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
</Storyboard>
|
||||
</VisualState>
|
||||
</VisualStateGroup>
|
||||
</VisualStateManager.VisualStateGroups>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="326" />
|
||||
<ColumnDefinition Width="*" />
|
||||
@@ -188,7 +211,7 @@
|
||||
</TabItem>
|
||||
</TabControl>
|
||||
</Grid>
|
||||
<Grid Grid.Column="1" Margin="{StaticResource PanelMargin}" HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
|
||||
<Grid x:Name="SlidersGrid" Grid.Column="1" Margin="{StaticResource PanelMargin}" HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="15" />
|
||||
<ColumnDefinition Width="*" MinWidth="120" />
|
||||
@@ -299,7 +322,7 @@
|
||||
/>
|
||||
</Grid>
|
||||
|
||||
<Grid Grid.Row="1" Grid.ColumnSpan="3" Margin="3">
|
||||
<Grid x:Name="PaletteTabsGrid" Grid.Row="1" Grid.ColumnSpan="3" Margin="3">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="*"/>
|
||||
@@ -311,7 +334,12 @@
|
||||
BorderThickness="2"
|
||||
Background="{Binding result, Converter={StaticResource ColorToBrushConverter}}"
|
||||
/>
|
||||
<TabControl ItemsSource="{Binding palettes}" Grid.Column="1" HorizontalAlignment="Stretch" ItemContainerStyle="{StaticResource PaletteTabItem}"/>
|
||||
<TabControl
|
||||
ItemsSource="{Binding palettes}"
|
||||
Grid.Column="1"
|
||||
HorizontalAlignment="Stretch"
|
||||
ItemContainerStyle="{StaticResource PaletteTabItem}"
|
||||
/>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</ControlTemplate>
|
||||
|
||||
@@ -24,7 +24,6 @@ namespace mbts.ui.controls.color_picker {
|
||||
partial class ColorPickerControl : Control {
|
||||
const string _max_saturation_color_key = "MaxSaturationColor";
|
||||
readonly PropertyUpdateGate _saturation_border_gate = new PropertyUpdateGate();
|
||||
readonly ColorPickerVM _color_vm = new ColorPickerVM();
|
||||
|
||||
DataGrid _named_colors_grid;
|
||||
Border _saturation_border;
|
||||
@@ -34,7 +33,6 @@ namespace mbts.ui.controls.color_picker {
|
||||
|
||||
public ColorPickerControl() {
|
||||
InitializeComponent();
|
||||
DataContext = _color_vm;
|
||||
CommandManager.AddExecutedHandler(this, executed_handler);
|
||||
CommandManager.AddCanExecuteHandler(this, can_execute_handler);
|
||||
|
||||
@@ -50,10 +48,10 @@ namespace mbts.ui.controls.color_picker {
|
||||
_named_colors_grid.CommitEdit();
|
||||
} else if (e.Parameter is NamedColorVM vm) {
|
||||
if (e.Command == ColorPickerSwatch.AssignColorCommand)
|
||||
vm.assign_color(_color_vm.result);
|
||||
vm.assign_color(Model.result);
|
||||
else if (e.Command == ColorPickerSwatch.NameColorCommand) {
|
||||
_named_colors_tab.IsSelected = true;
|
||||
_color_vm.selected_named_color = vm;
|
||||
Model.selected_named_color = vm;
|
||||
_named_colors_grid.ScrollIntoView(vm);
|
||||
_named_colors_grid.CurrentCell = new DataGridCellInfo(vm, _named_colors_grid.Columns[0]);
|
||||
}
|
||||
@@ -64,9 +62,26 @@ namespace mbts.ui.controls.color_picker {
|
||||
e.CanExecute = true;
|
||||
}
|
||||
|
||||
public event Action<Color> SelectedColorChanged;
|
||||
|
||||
public ColorPickerVM Model { get; } = new ColorPickerVM();
|
||||
|
||||
public static readonly DependencyProperty ShowControlsProperty =
|
||||
WPFHelper.create_dp<bool, ColorPickerControl>(nameof(ShowControls), true);
|
||||
|
||||
public bool ShowControls {
|
||||
get => (bool)GetValue(ShowControlsProperty);
|
||||
set => SetValue(ShowControlsProperty, value);
|
||||
}
|
||||
|
||||
#region SelectedColor
|
||||
public static readonly DependencyProperty SelectedColorProperty =
|
||||
WPFHelper.create_dp<Color, ColorPickerControl>(nameof(SelectedColor), (cp, val) => cp._color_vm.result = val);
|
||||
WPFHelper.create_dp<Color, ColorPickerControl>(
|
||||
nameof(SelectedColor),
|
||||
(cp, val) => {
|
||||
cp.Model.result = val;
|
||||
cp.SelectedColorChanged?.Invoke(val);
|
||||
});
|
||||
|
||||
public Color SelectedColor { get => (Color)GetValue(SelectedColorProperty); set => SetValue(SelectedColorProperty, value); }
|
||||
|
||||
@@ -95,17 +110,17 @@ namespace mbts.ui.controls.color_picker {
|
||||
// b) destroy and recreate color_vm without breaking TabControl bindings, and ICollectionView source dependencies
|
||||
void _on_palettes_collection_changed(object sender, NotifyCollectionChangedEventArgs e) {
|
||||
switch (e.Action) {
|
||||
case NotifyCollectionChangedAction.Add: _color_vm.palettes.InsertRange(e.NewStartingIndex, e.NewItems); break;
|
||||
case NotifyCollectionChangedAction.Remove: _color_vm.palettes.RemoveRange(e.OldStartingIndex, e.OldItems.Count); break;
|
||||
case NotifyCollectionChangedAction.Move: _color_vm.palettes.Move(e.OldStartingIndex, e.NewStartingIndex); break;
|
||||
case NotifyCollectionChangedAction.Add: Model.palettes.InsertRange(e.NewStartingIndex, e.NewItems); break;
|
||||
case NotifyCollectionChangedAction.Remove: Model.palettes.RemoveRange(e.OldStartingIndex, e.OldItems.Count); break;
|
||||
case NotifyCollectionChangedAction.Move: Model.palettes.Move(e.OldStartingIndex, e.NewStartingIndex); break;
|
||||
case NotifyCollectionChangedAction.Replace:
|
||||
_color_vm.palettes.RemoveRange(e.OldStartingIndex, e.OldItems.Count);
|
||||
_color_vm.palettes.InsertRange(e.NewStartingIndex, e.NewItems);
|
||||
Model.palettes.RemoveRange(e.OldStartingIndex, e.OldItems.Count);
|
||||
Model.palettes.InsertRange(e.NewStartingIndex, e.NewItems);
|
||||
break;
|
||||
case NotifyCollectionChangedAction.Reset:
|
||||
_color_vm.palettes.Clear();
|
||||
Model.palettes.Clear();
|
||||
if (sender is IEnumerable<ColorPaletteVM> items)
|
||||
_color_vm.palettes.AddRange(items);
|
||||
Model.palettes.AddRange(items);
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -119,17 +134,17 @@ namespace mbts.ui.controls.color_picker {
|
||||
_saturation_border = this.assert_template_child<Border>("_saturation_border");
|
||||
_sample_image = this.assert_template_child<Image>("_sample_image");
|
||||
// TabItems do not inherit DataContext properly from its visual parent, which breaks certain bindings
|
||||
_named_colors_grid.SetBinding(DataGrid.ItemsSourceProperty, new Binding(nameof(ColorPickerVM.all_colors_view)) { Source = _color_vm });
|
||||
_named_colors_grid.SetBinding(DataGrid.SelectedItemProperty, new Binding(nameof(ColorPickerVM.selected_named_color)) { Source = _color_vm });
|
||||
_color_vm.PropertyChanged += color_vm_property_changed;
|
||||
_color_vm.result = SelectedColor;
|
||||
_named_colors_grid.SetBinding(DataGrid.ItemsSourceProperty, new Binding(nameof(ColorPickerVM.all_colors_view)) { Source = Model });
|
||||
_named_colors_grid.SetBinding(DataGrid.SelectedItemProperty, new Binding(nameof(ColorPickerVM.selected_named_color)) { Source = Model });
|
||||
Model.PropertyChanged += color_vm_property_changed;
|
||||
Model.result = SelectedColor;
|
||||
IDisposable mouse_move_hook = null;
|
||||
_saturation_border.MouseDown += saturation_border_mouse_down;
|
||||
_saturation_border.MouseUp += saturation_border_mouse_up;
|
||||
|
||||
_template_hooks = Disposable.Create(() => {
|
||||
_named_colors_grid.clear_bindings(DataGrid.ItemsSourceProperty, DataGrid.SelectedItemProperty);
|
||||
_color_vm.PropertyChanged -= color_vm_property_changed;
|
||||
Model.PropertyChanged -= color_vm_property_changed;
|
||||
_saturation_border.MouseDown -= saturation_border_mouse_down;
|
||||
_saturation_border.MouseUp -= saturation_border_mouse_up;
|
||||
Disposable.dispose(ref mouse_move_hook);
|
||||
@@ -138,7 +153,7 @@ namespace mbts.ui.controls.color_picker {
|
||||
void color_vm_property_changed(object sender, PropertyChangedEventArgs e) {
|
||||
switch (e.PropertyName) {
|
||||
case nameof(ColorPickerVM.max_saturation): _update_saturation_img(); break;
|
||||
case nameof(ColorPickerVM.result): SelectedColor = _color_vm.result; break;
|
||||
case nameof(ColorPickerVM.result): SelectedColor = Model.result; break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -162,7 +177,7 @@ namespace mbts.ui.controls.color_picker {
|
||||
var left_adj = ColorUtils.adjust_color(tr, Colors.White, left);
|
||||
var result = ColorUtils.adjust_color(left_adj, Colors.Black, down);
|
||||
using (_saturation_border_gate.update())
|
||||
_color_vm.result = result;
|
||||
Model.result = result;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -173,7 +188,7 @@ namespace mbts.ui.controls.color_picker {
|
||||
|
||||
void _update_saturation_img() =>
|
||||
_saturation_border_gate.try_update(() =>
|
||||
Resources[_max_saturation_color_key] = _color_vm.max_saturation);
|
||||
Resources[_max_saturation_color_key] = Model.max_saturation);
|
||||
|
||||
void _sample_image_click(BitmapSource img, Point pos) {
|
||||
// https://social.msdn.microsoft.com/Forums/vstudio/en-US/82a5731e-e201-4aaf-8d4b-062b138338fe/getting-pixel-information-from-a-bitmapimage?forum=wpf
|
||||
@@ -188,9 +203,9 @@ namespace mbts.ui.controls.color_picker {
|
||||
byte red = pixels[index];
|
||||
byte green = pixels[index + 1];
|
||||
byte blue = pixels[index + 2];
|
||||
byte alpha = pixels[index + 3];
|
||||
byte alpha = Model.alpha;
|
||||
var color = Color.FromArgb(alpha, blue, green, red);
|
||||
_color_vm.result = color;
|
||||
Model.result = color;
|
||||
}
|
||||
|
||||
void _sample_image_mouse_down(object sender, MouseButtonEventArgs e) {
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
<Window
|
||||
x:Class="mbts.ui.controls.color_picker.ColorPickerDialog"
|
||||
x:ClassModifier="internal"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:u="clr-namespace:mbts.ui"
|
||||
xmlns:c="clr-namespace:mbts.ui.controls"
|
||||
xmlns:cv="clr-namespace:mbts.ui.converters"
|
||||
xmlns:cp="clr-namespace:mbts.ui.controls.color_picker"
|
||||
u:WindowStyle.SizeToContent="WidthAndHeight"
|
||||
u:WindowStyle.AllowResize="False"
|
||||
Style="{StaticResource BTSDialog}"
|
||||
>
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="*"/>
|
||||
</Grid.RowDefinitions>
|
||||
<cp:ColorPickerControl x:Name="color_picker"/>
|
||||
<c:DialogSkirtOKCancel
|
||||
x:Name="dialog_skirt"
|
||||
Grid.Row="1"
|
||||
BorderThickness="0"
|
||||
/>
|
||||
</Grid>
|
||||
</Window>
|
||||
@@ -0,0 +1,47 @@
|
||||
using bts.utils;
|
||||
using mbts.spark.utilities;
|
||||
using mbts.ui.extensions;
|
||||
using mbts.ui.utility;
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Collections.Specialized;
|
||||
using System.ComponentModel;
|
||||
using System.Globalization;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Data;
|
||||
using System.Windows.Documents;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Media.Imaging;
|
||||
|
||||
namespace mbts.ui.controls.color_picker {
|
||||
/// <summary>
|
||||
/// Interaction logic for ColorPickerControl.xaml
|
||||
/// </summary>
|
||||
partial class ColorPickerDialog : Window {
|
||||
public ColorPickerDialog() =>
|
||||
InitializeComponent();
|
||||
|
||||
public static Color pick_color_modal(Window owner = null, Color? init = null, ObservableCollection<ColorPaletteVM> palettes = null) {
|
||||
var result = Colors.Transparent;
|
||||
var cpd = new ColorPickerDialog {
|
||||
WindowStartupLocation = WindowStartupLocation.CenterOwner
|
||||
};
|
||||
if (palettes != null) {
|
||||
cpd.color_picker.Palettes = palettes;
|
||||
cpd.color_picker.ShowControls = true;
|
||||
}
|
||||
if (init.HasValue)
|
||||
cpd.color_picker.SelectedColor = init.Value;
|
||||
cpd.dialog_skirt.OKCommand = new DelegateCommand(() => {
|
||||
result = cpd.color_picker.SelectedColor;
|
||||
});
|
||||
cpd.Owner = owner;
|
||||
cpd.ShowDialog();
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,115 @@
|
||||
<UserControl
|
||||
x:Class="mbts.ui.controls.color_picker.ColorPickerSuite"
|
||||
x:ClassModifier="internal"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:u="clr-namespace:mbts.ui"
|
||||
xmlns:c="clr-namespace:mbts.ui.controls"
|
||||
xmlns:cv="clr-namespace:mbts.ui.converters"
|
||||
xmlns:cp="clr-namespace:mbts.ui.controls.color_picker"
|
||||
xmlns:sys="clr-namespace:System;assembly=mscorlib"
|
||||
FocusVisualStyle="{x:Null}"
|
||||
Width="437"
|
||||
>
|
||||
<UserControl.Resources>
|
||||
<!--<sys:Byte x:Key="LowAlpha">0x4C</sys:Byte>-->
|
||||
<cv:MultiValueConverterPipe x:Key="LowAlphaToolTipVisibilityConverter">
|
||||
<cv:DynamicIsLessThanConverter/>
|
||||
<cv:BooleanToObjectConverter ValueForFalse="{x:Static Visibility.Collapsed}" ValueForTrue="{x:Static Visibility.Visible}"/>
|
||||
</cv:MultiValueConverterPipe>
|
||||
</UserControl.Resources>
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="*"/>
|
||||
</Grid.RowDefinitions>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<c:TabularColorPicker
|
||||
x:Name="_tabular_color_picker"
|
||||
SelectedValuePath="color"
|
||||
ItemsSource="{x:Static cp:ColorUtils.WebColorVMGrid}"
|
||||
SelectedValue="{Binding Model.selected_named_color, RelativeSource={RelativeSource AncestorType=cp:ColorPickerSuite}, Mode=TwoWay}"
|
||||
SelectedOpacity="{Binding Model.opacity, RelativeSource={RelativeSource AncestorType=cp:ColorPickerSuite}}"
|
||||
>
|
||||
<c:TabularColorPicker.ItemContainerStyle>
|
||||
<Style TargetType="ListBoxItem" BasedOn="{StaticResource TabularColorPickerItemStyle}">
|
||||
<!--ToolTips are not in the same VisualTree as their owner, and for some reason the ListBoxItem's Parent is null,
|
||||
so there isn't a direct binding path from the ToolTip to the ColorPickerSuite.Model.opacity;
|
||||
the 'Tag' binding is a workaround to make the ColorPickerSuite available to the ToolTip-->
|
||||
<Setter Property="Tag" Value="{Binding RelativeSource={RelativeSource AncestorType=cp:ColorPickerSuite}}"/>
|
||||
<Setter Property="ToolTip">
|
||||
<Setter.Value>
|
||||
<ToolTip Content="{Binding}">
|
||||
<ToolTip.ContentTemplate>
|
||||
<DataTemplate>
|
||||
<StackPanel>
|
||||
<u:BindingProxy x:Name="LowAlphaProxy" Value="{DynamicResource LowAlpha}"/>
|
||||
<TextBlock Text="{Binding name, Converter={StaticResource TitleCaseConverter}}" HorizontalAlignment="Left"/>
|
||||
<TextBlock HorizontalAlignment="Left">
|
||||
<TextBlock.Visibility>
|
||||
<MultiBinding Converter="{StaticResource LowAlphaToolTipVisibilityConverter}">
|
||||
<Binding Path="PlacementTarget.Tag.Model.opacity" RelativeSource="{RelativeSource AncestorType=ToolTip}"/>
|
||||
<Binding Path="Value" ElementName="LowAlphaProxy"/>
|
||||
</MultiBinding>
|
||||
</TextBlock.Visibility>
|
||||
<Run>The color may be difficult to see because the opacity is low.</Run>
|
||||
<LineBreak/>
|
||||
<Run FontStyle="Italic">Hint: Use the slider on the right to adjust the opacity level.</Run>
|
||||
</TextBlock>
|
||||
</StackPanel>
|
||||
</DataTemplate>
|
||||
</ToolTip.ContentTemplate>
|
||||
</ToolTip>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
</c:TabularColorPicker.ItemContainerStyle>
|
||||
</c:TabularColorPicker>
|
||||
<Grid Grid.Column="1" Margin="4,0,0,0" Width="45" ToolTip="Opacity">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="*"/>
|
||||
</Grid.RowDefinitions>
|
||||
<TextBlock HorizontalAlignment="Right">
|
||||
<TextBlock.Text>
|
||||
<MultiBinding StringFormat="P" Converter="{x:Static cv:ValueConverters.SliderPercentage}">
|
||||
<Binding Path="Value" ElementName="_opacity_slider"/>
|
||||
<Binding Path="Minimum" ElementName="_opacity_slider"/>
|
||||
<Binding Path="Maximum" ElementName="_opacity_slider"/>
|
||||
</MultiBinding>
|
||||
</TextBlock.Text>
|
||||
</TextBlock>
|
||||
<Slider
|
||||
x:Name="_opacity_slider"
|
||||
Grid.Row="1"
|
||||
HorizontalAlignment="Center"
|
||||
Orientation="Vertical"
|
||||
Minimum="0"
|
||||
Maximum="255"
|
||||
Value="{Binding Model.opacity, RelativeSource={RelativeSource AncestorType=cp:ColorPickerSuite}}"
|
||||
/>
|
||||
</Grid>
|
||||
<c:PopupControl
|
||||
Grid.Row="1"
|
||||
Grid.ColumnSpan="2"
|
||||
Padding="6"
|
||||
HorizontalAlignment="Stretch"
|
||||
HorizontalContentAlignment="Left"
|
||||
BorderThickness="0"
|
||||
ShowExpanderArrow="False"
|
||||
PopupPlacement="Bottom"
|
||||
ButtonStyle="{StaticResource AddItemButton}"
|
||||
Header="Custom"
|
||||
>
|
||||
<cp:ColorPickerControl
|
||||
x:Name="_color_picker"
|
||||
Padding="0,0,6,0"
|
||||
Palettes="{Binding Model.palettes, RelativeSource={RelativeSource AncestorType=cp:ColorPickerSuite}}"
|
||||
SelectedColor="{Binding Model.selected_color, RelativeSource={RelativeSource AncestorType=cp:ColorPickerSuite}, Mode=TwoWay}"
|
||||
/>
|
||||
</c:PopupControl>
|
||||
</Grid>
|
||||
</UserControl>
|
||||
@@ -0,0 +1,126 @@
|
||||
using bts.utils;
|
||||
using mbts.ui.shell;
|
||||
using mbts.ui.utility;
|
||||
using System;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Media;
|
||||
|
||||
namespace mbts.ui.controls.color_picker {
|
||||
/// <summary>
|
||||
/// Interaction logic for ColorPickerControl.xaml
|
||||
/// </summary>
|
||||
partial class ColorPickerSuite : UserControl {
|
||||
public ColorPickerSuite() {
|
||||
Model = new ColorModel(this);
|
||||
InitializeComponent();
|
||||
Model.on_palettes(Palettes);
|
||||
}
|
||||
|
||||
public static readonly DependencyProperty SelectedColorProperty =
|
||||
WPFHelper.create_dp<Color, ColorPickerSuite>(
|
||||
nameof(SelectedColor),
|
||||
FrameworkPropertyMetadataOptions.BindsTwoWayByDefault,
|
||||
(o, v) => o.Model.selected_color = v);
|
||||
|
||||
public Color SelectedColor {
|
||||
get => (Color)GetValue(SelectedColorProperty);
|
||||
set => SetValue(SelectedColorProperty, value);
|
||||
}
|
||||
|
||||
public static readonly DependencyProperty PalettesProperty =
|
||||
WPFHelper.create_dp<ObservableCollection<ColorPaletteVM>, ColorPickerSuite>(
|
||||
nameof(Palettes),
|
||||
(o, v) => {
|
||||
o.Model.on_palettes(v);
|
||||
});
|
||||
|
||||
public ObservableCollection<ColorPaletteVM> Palettes {
|
||||
get => GetValue(PalettesProperty) as ObservableCollection<ColorPaletteVM>;
|
||||
set => SetValue(PalettesProperty, value);
|
||||
}
|
||||
|
||||
public ColorModel Model { get; }
|
||||
|
||||
public class ColorModel : ViewModelBase {
|
||||
readonly ColorPickerSuite _owner;
|
||||
// Note: 'alpha' and 'opacity' and 'A' (when in the context of 'argb') may be used interchangebly in the comments below.
|
||||
// -----------------------------------
|
||||
/// There is an important difference between <see cref="selected_color"/> and <see cref="selected_named_color"/>.
|
||||
/// The latter is bound to the SelectedValue of the <see cref="TabularColorPicker"/> (TCP), which does not have any opacity input controls.
|
||||
/// while the former is manually to
|
||||
/// For this reason, TCP must be immune to alpha channel adjustments, made in other controls (<see cref="ColorPickerSuite"/>, and <see cref="ColorPickerControl"/>, both have alpha/opacity sliders).
|
||||
/// To achieve this affect, when
|
||||
|
||||
/// To achieve this affect, the latter's Alpha should always be 255 (100% opacity).
|
||||
/// In the <see cref="selected_named_color"/> setter, only the RGB values are forwarded to the 'selected_color' property.
|
||||
/// This 'alpha buffer' works both ways:
|
||||
/// - it prevents the TCP from changing the selected_color's alpha to 1 everytime a tabular color item is selected.
|
||||
/// - and it prevents the alpha sliders from changing TCP's SelectedValue everytime they are adjusted.
|
||||
///
|
||||
Color
|
||||
_selected_color,
|
||||
_selected_named_color;
|
||||
IDisposable _palettes_hook;
|
||||
Action _init;
|
||||
|
||||
public ColorModel(ColorPickerSuite owner) {
|
||||
_owner = owner;
|
||||
_init = () => {
|
||||
/// This model demuxes color selection changes from 3 different controls, to a single dependency property (<see cref="SelectedColorProperty"/>).
|
||||
/// Note the 3 controls are: <see cref="ColorPickerControl"/>, <see cref="TabularColorPicker"/>, <see cref="ColorPickerDialog"/>.
|
||||
|
||||
/// Each of the aforementioned controls is bound to one or more properties in this class (<see cref="selected_color"/>, <see cref="selected_named_color"/>, <see cref="palettes"/>).
|
||||
/// In this design scheme, those controls are the binding targets, and this class the binding source.
|
||||
/// As the 'binding source,' this class must initialize the 'binding targets' with the appropriate properties.
|
||||
/// Otherwise, their bogus default values will be assigned to this class, and in turn used to update the binding source of the <see cref="SelectedColorProperty"/>.
|
||||
/// In other words, the viewmodel's prop that is bound to <see cref="SelectedColorProperty"/> would be overwritten with the bogus default value.
|
||||
|
||||
/// To eschew this mess, the code below is executed before the other controls are allowed to affect the <see cref="selected_color"/> property.
|
||||
/// Thus, they are initialized with the selected color provided by the binding source (aka viewmodel) of the <see cref="SelectedColorProperty"/>, rather than the other way around.
|
||||
_owner.GetBindingExpression(SelectedColorProperty)?.UpdateTarget();
|
||||
_owner._color_picker.SelectedColorChanged += c => selected_color = c;
|
||||
};
|
||||
}
|
||||
|
||||
public Color selected_color {
|
||||
get => _selected_color;
|
||||
set {
|
||||
InvokeOnce.invoke(ref _init);
|
||||
var alpha = _selected_color.A;
|
||||
if (Set(ref _selected_color, value)) {
|
||||
_owner.SetCurrentValue(SelectedColorProperty, value);
|
||||
if (_selected_color.A != alpha)
|
||||
RaisePropertyChanged(nameof(opacity));
|
||||
Set(ref _selected_named_color, value.with(255), nameof(selected_named_color));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public Color selected_named_color {
|
||||
get => _selected_named_color;
|
||||
set {
|
||||
InvokeOnce.invoke(ref _init);
|
||||
if (Set(ref _selected_named_color, value)) {
|
||||
if (value != null)
|
||||
selected_color = value.with(opacity);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public byte opacity {
|
||||
get => _selected_color.A;
|
||||
set => selected_color = _selected_color.with(value);
|
||||
}
|
||||
|
||||
public ObservableCollection<ColorPaletteVM> palettes { get; } = new ObservableCollection<ColorPaletteVM>();
|
||||
|
||||
public void on_palettes(ObservableCollection<ColorPaletteVM> new_palettes) {
|
||||
Disposable.dispose(ref _palettes_hook);
|
||||
if (new_palettes != null)
|
||||
_palettes_hook = ObservableAutoWrapper.connect(new_palettes, palettes);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,28 +1,24 @@
|
||||
<ListBox
|
||||
<c:TabularColorPicker
|
||||
x:Class="mbts.ui.controls.color_picker.ColorPickerSwatch"
|
||||
x:ClassModifier="internal"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:c="clr-namespace:mbts.ui.controls"
|
||||
xmlns:cp="clr-namespace:mbts.ui.controls.color_picker"
|
||||
Margin="0"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Stretch"
|
||||
ScrollViewer.HorizontalScrollBarVisibility="Disabled"
|
||||
>
|
||||
<ListBox.Resources>
|
||||
<FrameworkElement.Resources>
|
||||
<cp:NamedColorItemToolTipConverter x:Key="ToolTipConverter"/>
|
||||
</ListBox.Resources>
|
||||
<ListBox.ItemsPanel>
|
||||
</FrameworkElement.Resources>
|
||||
<ItemsControl.ItemsPanel>
|
||||
<ItemsPanelTemplate>
|
||||
<WrapPanel Orientation="Horizontal"/>
|
||||
</ItemsPanelTemplate>
|
||||
</ListBox.ItemsPanel>
|
||||
<ListBox.ItemContainerStyle>
|
||||
<Style TargetType="ListBoxItem" BasedOn="{StaticResource {x:Type ListBoxItem}}">
|
||||
<Setter Property="Cursor" Value="Hand"/>
|
||||
</Style>
|
||||
</ListBox.ItemContainerStyle>
|
||||
<ListBox.ItemTemplate>
|
||||
</ItemsControl.ItemsPanel>
|
||||
<!--<ListBox.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<Border
|
||||
BorderBrush="Black"
|
||||
@@ -40,5 +36,5 @@
|
||||
</Border.ToolTip>
|
||||
</Border>
|
||||
</DataTemplate>
|
||||
</ListBox.ItemTemplate>
|
||||
</ListBox>
|
||||
</ListBox.ItemTemplate>-->
|
||||
</c:TabularColorPicker>
|
||||
@@ -9,7 +9,7 @@ namespace mbts.ui.controls.color_picker {
|
||||
/// <summary>
|
||||
/// Interaction logic for ColorPickerSwatch.xaml
|
||||
/// </summary>
|
||||
partial class ColorPickerSwatch : ListBox {
|
||||
partial class ColorPickerSwatch : TabularColorPicker {
|
||||
public ColorPickerSwatch() =>
|
||||
InitializeComponent();
|
||||
|
||||
|
||||
@@ -0,0 +1,123 @@
|
||||
<ResourceDictionary
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:u="clr-namespace:mbts.ui"
|
||||
xmlns:c="clr-namespace:mbts.ui.controls"
|
||||
xmlns:cp="clr-namespace:mbts.ui.controls.color_picker"
|
||||
>
|
||||
<ControlTemplate x:Key="TabularColorPickerTemplate" TargetType="c:TabularColorPicker">
|
||||
<Border
|
||||
Background="{TemplateBinding Background}"
|
||||
BorderBrush="{TemplateBinding BorderBrush}"
|
||||
BorderThickness="{TemplateBinding BorderThickness}"
|
||||
HorizontalAlignment="{TemplateBinding HorizontalAlignment}"
|
||||
VerticalAlignment="{TemplateBinding VerticalAlignment}"
|
||||
Width="{TemplateBinding Width}"
|
||||
Height="{TemplateBinding Height}"
|
||||
Padding="{TemplateBinding Padding}"
|
||||
>
|
||||
<ItemsPresenter/>
|
||||
</Border>
|
||||
</ControlTemplate>
|
||||
|
||||
<Style x:Key="TabularColorPickerItemStyle" TargetType="ListBoxItem">
|
||||
<Setter Property="Padding" Value="0"/>
|
||||
<Setter Property="BorderThickness" Value="1"/>
|
||||
<Setter Property="Width" Value="19"/>
|
||||
</Style>
|
||||
|
||||
<DataTemplate x:Key="NamedColorTemplate" DataType="{x:Type cp:NamedColorVM}">
|
||||
<Border
|
||||
x:Name="Bd"
|
||||
BorderThickness="1"
|
||||
Width="18"
|
||||
Height="18"
|
||||
>
|
||||
<Border.BorderBrush>
|
||||
<SolidColorBrush Color="{Binding color}"/>
|
||||
</Border.BorderBrush>
|
||||
<Border.Background>
|
||||
<SolidColorBrush Color="{Binding color}"/>
|
||||
</Border.Background>
|
||||
</Border>
|
||||
<DataTemplate.Triggers>
|
||||
<DataTrigger Binding="{Binding IsMouseOver, RelativeSource={RelativeSource AncestorType=ListBoxItem}}" Value="True">
|
||||
<Setter TargetName="Bd" Property="BorderBrush" Value="Black"/>
|
||||
</DataTrigger>
|
||||
<DataTrigger Binding="{Binding IsSelected, RelativeSource={RelativeSource AncestorType=ListBoxItem}}" Value="True">
|
||||
<Setter TargetName="Bd" Property="BorderBrush" Value="Black"/>
|
||||
</DataTrigger>
|
||||
</DataTemplate.Triggers>
|
||||
</DataTemplate>
|
||||
|
||||
<ControlTemplate x:Key="TabularColorPickerItemTemplate" TargetType="ListBoxItem">
|
||||
<Border
|
||||
x:Name="RootBorder"
|
||||
BorderThickness="{TemplateBinding BorderThickness}"
|
||||
Width="{TemplateBinding Width}"
|
||||
Height="{TemplateBinding Height}"
|
||||
HorizontalAlignment="{TemplateBinding HorizontalAlignment}"
|
||||
VerticalAlignment="{TemplateBinding VerticalAlignment}"
|
||||
Padding="{TemplateBinding Padding}"
|
||||
>
|
||||
<u:StateManager.Groups>
|
||||
<u:StateGroupsManager AttachToMembers="True">
|
||||
<u:StateDefinitionCollection>
|
||||
<u:MouseStateDefinition MouseSource="{Binding Group.Target, RelativeSource={RelativeSource Self}}"/>
|
||||
</u:StateDefinitionCollection>
|
||||
</u:StateGroupsManager>
|
||||
</u:StateManager.Groups>
|
||||
<VisualStateManager.VisualStateGroups>
|
||||
<VisualStateGroup Name="MouseStates">
|
||||
<VisualState Name="Normal"/>
|
||||
<VisualState Name="MouseOver">
|
||||
<Storyboard>
|
||||
<ColorAnimation Storyboard.TargetName="RootBorder" Storyboard.TargetProperty="BorderBrush.Color" BeginTime="0" Duration="0" To="#80FFFFFF"/>
|
||||
<DoubleAnimation Storyboard.TargetName="RootBorder" Storyboard.TargetProperty="BorderBrush.Opacity" BeginTime="0" Duration="0" To="1"/>
|
||||
</Storyboard>
|
||||
</VisualState>
|
||||
<VisualState Name="Pressed">
|
||||
<Storyboard>
|
||||
<DoubleAnimation Storyboard.TargetName="RootBorder" Storyboard.TargetProperty="Background.Opacity" BeginTime="0" Duration="0" To="0.5"/>
|
||||
<ColorAnimation Storyboard.TargetName="RootBorder" Storyboard.TargetProperty="BorderBrush.Color" BeginTime="0" Duration="0" To="#80FFFFFF"/>
|
||||
<DoubleAnimation Storyboard.TargetName="RootBorder" Storyboard.TargetProperty="BorderBrush.Opacity" BeginTime="0" Duration="0" To="1"/>
|
||||
</Storyboard>
|
||||
</VisualState>
|
||||
</VisualStateGroup>
|
||||
<VisualStateGroup Name="SelectionStates">
|
||||
<VisualState Name="Unselected"/>
|
||||
<VisualState Name="Selected">
|
||||
<Storyboard>
|
||||
<DoubleAnimation Storyboard.TargetName="RootBorder" Storyboard.TargetProperty="BorderBrush.Opacity" BeginTime="0" Duration="0" To="1"/>
|
||||
</Storyboard>
|
||||
</VisualState>
|
||||
</VisualStateGroup>
|
||||
</VisualStateManager.VisualStateGroups>
|
||||
<Border.Background>
|
||||
<SolidColorBrush Color="{Binding color}" Opacity="1"/>
|
||||
</Border.Background>
|
||||
<Border.BorderBrush>
|
||||
<SolidColorBrush Color="Black" Opacity="0"/>
|
||||
</Border.BorderBrush>
|
||||
<Grid>
|
||||
<ContentPresenter
|
||||
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
|
||||
VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
|
||||
/>
|
||||
</Grid>
|
||||
</Border>
|
||||
</ControlTemplate>
|
||||
|
||||
<Style TargetType="c:TabularColorPicker">
|
||||
<Setter Property="Template" Value="{StaticResource TabularColorPickerTemplate}"/>
|
||||
<Setter Property="ItemsSource" Value="{x:Static cp:ColorUtils.WebColorVMs}"/>
|
||||
<Setter Property="ItemTemplate" Value="{StaticResource NamedColorTemplate}"/>
|
||||
<Setter Property="ItemsPanel">
|
||||
<Setter.Value>
|
||||
<ItemsPanelTemplate>
|
||||
<UniformGrid Rows="7"/>
|
||||
</ItemsPanelTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
</ResourceDictionary>
|
||||
@@ -0,0 +1,25 @@
|
||||
using mbts.ui.converters;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Data;
|
||||
|
||||
namespace mbts.ui.controls {
|
||||
/// <summary>
|
||||
/// Interaction logic for ColorPickerPopup.xaml
|
||||
/// </summary>
|
||||
class TabularColorPicker : ListBox {
|
||||
public static readonly IValueConverter AlphaToOpacityConverter =
|
||||
ValueConverter.create<byte, double>(alpha => alpha / 255.0);
|
||||
|
||||
static TabularColorPicker() =>
|
||||
DefaultStyleKeyProperty.OverrideMetadata(typeof(TabularColorPicker), new FrameworkPropertyMetadata(typeof(TabularColorPicker)));
|
||||
|
||||
public static readonly DependencyProperty SelectedOpacityProperty =
|
||||
WPFHelper.create_dp<byte, TabularColorPicker>(nameof(SelectedOpacity), 255);
|
||||
|
||||
public byte SelectedOpacity {
|
||||
get => (byte)GetValue(SelectedOpacityProperty);
|
||||
set => SetValue(SelectedOpacityProperty, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
using mbts.ui.converters;
|
||||
using mbts.ui.dpi;
|
||||
using mbts.ui.converters;
|
||||
using mbts.ui.shell;
|
||||
using mbts.ui.utility.text;
|
||||
using System;
|
||||
@@ -10,6 +11,7 @@ using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Data;
|
||||
using System.Windows.Media;
|
||||
using bts;
|
||||
|
||||
namespace mbts.ui.controls {
|
||||
partial class DIYGrid {
|
||||
@@ -84,12 +86,11 @@ namespace mbts.ui.controls {
|
||||
}
|
||||
|
||||
public static Rect? get_clip(Size content_size, double row_height, double left, double unclipped_left, double width) =>
|
||||
content_size == null ? get_clip(0, 0, row_height, left, unclipped_left, width) :
|
||||
get_clip(content_size.Width, content_size.Height, row_height, left, unclipped_left, width);
|
||||
get_clip(content_size.Width, content_size.Height, row_height, left, unclipped_left, width);
|
||||
|
||||
public static Rect? get_clip(double content_width, double content_height, double row_height, double left, double unclipped_left, double width) {
|
||||
if (content_width > width || unclipped_left < left)
|
||||
return new Rect(left, 0, width - left + unclipped_left, row_height);
|
||||
return new Rect(left, 0, width - left + unclipped_left, row_height + 1);
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -97,7 +98,10 @@ namespace mbts.ui.controls {
|
||||
draw_with_clip(dc,
|
||||
raw_content_width + padding.Left + padding.Right,
|
||||
raw_content_height + padding.Top + padding.Bottom,
|
||||
row_height, position.left, position.unclipped_left, position.width);
|
||||
row_height,
|
||||
position.left.RoundLayoutValue(),
|
||||
position.unclipped_left.RoundLayoutValue(),
|
||||
position.width.RoundLayoutValue());
|
||||
|
||||
public static IDisposable draw_with_clip(DrawingContext dc, double content_width, double content_height, double row_height, double left, double unclipped_left, double width) {
|
||||
var clip = get_clip(content_width, content_height, row_height, left, unclipped_left, width);
|
||||
@@ -108,54 +112,38 @@ namespace mbts.ui.controls {
|
||||
}
|
||||
|
||||
public static void draw_text(
|
||||
DrawingContext dc, GlyphContext glyph_context, double font_size, string text, ColumnGeometry position, double row_height,
|
||||
Brush foreground, Brush background, HorizontalAlignment x_align, VerticalAlignment y_align, Thickness padding) {
|
||||
DrawingContext dc,
|
||||
GlyphContext glyph_context,
|
||||
double font_size,
|
||||
string text,
|
||||
ColumnGeometry position,
|
||||
double row_height,
|
||||
Brush foreground,
|
||||
Brush background,
|
||||
HorizontalAlignment x_align,
|
||||
VerticalAlignment y_align,
|
||||
Thickness padding,
|
||||
bool round_to_layout = true) {
|
||||
//
|
||||
if (!string.IsNullOrEmpty(text)) {
|
||||
var gr = glyph_context.get_glyph_run(text, font_size, position.unclipped_left, 0, position.right, row_height, x_align, y_align, padding);
|
||||
draw_text(dc, gr, gr.AdvanceWidths.Sum(), font_size, position, padding, row_height, foreground, background);
|
||||
}
|
||||
var gr = glyph_context.get_glyph_run(text, font_size, position.unclipped_left, 0, position.right, row_height, x_align, y_align, padding, round_to_layout);
|
||||
var gwidth = gr == null ? 0 : gr.ComputeAlignmentBox().Width;
|
||||
draw_text(dc, gr, gwidth, font_size, position, padding, row_height, foreground, background);
|
||||
}
|
||||
|
||||
public static void draw_text(DrawingContext dc, GlyphRun glyph_run, double glyph_width, double font_size, ColumnGeometry position, Thickness padding, double row_height, Brush foreground, Brush background) {
|
||||
using (draw_with_clip(dc, glyph_width, font_size, padding, row_height, position)) {
|
||||
if (background != null)
|
||||
dc.DrawRectangle(background, null, new Rect(position.left, 0, position.width, row_height));
|
||||
dc.DrawGlyphRun(foreground, glyph_run);
|
||||
if (glyph_run == null && background == null)
|
||||
return;
|
||||
var rect = position.to_rect(row_height);
|
||||
if (background != null) {
|
||||
var guidelines = rect.AntiAliasGuidelines(0);
|
||||
dc.PushGuidelineSet(guidelines);
|
||||
dc.DrawRectangle(background, null, rect);
|
||||
dc.Pop();
|
||||
}
|
||||
}
|
||||
|
||||
public static void draw_text(
|
||||
DrawingContext dc, GlyphContext glyph_context, double font_size, string text, ColumnGeometry position, double row_height,
|
||||
Brush foreground, Brush background, HorizontalAlignment x_align, VerticalAlignment y_align, Thickness padding, IEnumerable<CellAnimation> trigger_actions) {
|
||||
//
|
||||
using (position.clip_draw(dc, row_height)) {
|
||||
double adjusted_left = position.unclipped_left;
|
||||
double available_width = position.width;
|
||||
if (background != null)
|
||||
dc.DrawRectangle(background, null, new Rect(position.left, 0, position.width, row_height));
|
||||
// store any trigger-glyphs in a list, to be drawn after iterating through all triggers (must draw background first, so glyphs will not be covered up).
|
||||
List<(Brush brush, GlyphRun glyph)> glyphs = new List<(Brush brush, GlyphRun glyph)>(1);
|
||||
if (trigger_actions != null) {
|
||||
foreach (CellAnimation ta in trigger_actions) {
|
||||
switch (ta.animation_type_case) {
|
||||
case CellAnimation.AnimationTypeOneofCase.BackgroundAnimation:
|
||||
dc.DrawRectangle(ta.background_animation.brush, ta.background_animation.pen, new Rect(position.left, 0, position.width, row_height));
|
||||
break;
|
||||
case CellAnimation.AnimationTypeOneofCase.SymbolAnimation:
|
||||
var (symbol_text, is_sideways) = _get_symbol_animation_args(ta.symbol_animation);
|
||||
GlyphRun symbol_glyph = glyph_context.get_glyph_run(symbol_text, font_size, position.width, row_height, HorizontalAlignment.Left, adjusted_left, y_align, 0, is_sideways: is_sideways);
|
||||
glyphs.Add((ta.symbol_animation.brush, symbol_glyph));
|
||||
adjusted_left += symbol_glyph.AdvanceWidths.Sum();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
foreach (var (brush, glyph) in glyphs) {
|
||||
dc.DrawGlyphRun(brush, glyph);
|
||||
}
|
||||
GlyphRun main_content = glyph_context.get_glyph_run(text, font_size, adjusted_left, 0, position.right, row_height, x_align, y_align, padding);
|
||||
draw_text(dc, main_content, main_content.AdvanceWidths.Sum(), font_size, position, padding, row_height, foreground, background);
|
||||
if (glyph_run != null) {
|
||||
dc.PushClip(new RectangleGeometry(rect));
|
||||
dc.DrawGlyphRun(foreground, glyph_run);
|
||||
dc.Pop();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -13,6 +13,12 @@ using System.Windows.Controls;
|
||||
using System.Windows.Media;
|
||||
|
||||
namespace mbts.ui.controls {
|
||||
class DIYGridColumn : DIYGrid.ColumnBase<string> {
|
||||
protected override void on_key_initialized(string key) {
|
||||
if (id == null && key != null)
|
||||
id = key.ToString();
|
||||
}
|
||||
}
|
||||
// Column types are nested inside the DIYGrid
|
||||
partial class DIYGrid {
|
||||
public static class DefaultColumnWidths {
|
||||
|
||||
@@ -1,30 +1,39 @@
|
||||
using System;
|
||||
using System.Windows;
|
||||
using System.Windows.Media;
|
||||
|
||||
namespace mbts.ui.controls {
|
||||
// Column types are nested inside the DIYGrid
|
||||
partial class DIYGrid {
|
||||
public struct ColumnGeometry {
|
||||
public ColumnBase column;
|
||||
public DIYGridSuperHeader super_column;
|
||||
public double unclipped_left; /* If the column was unclipped */
|
||||
public double left;
|
||||
public double width { get { return column.ActualWidth; } }
|
||||
public double right { get { return unclipped_left + width; } }
|
||||
public readonly struct ColumnGeometry {
|
||||
public readonly ColumnBase column;
|
||||
public readonly DIYGridSuperHeader super_column;
|
||||
public readonly double
|
||||
left,
|
||||
// If the column was unclipped
|
||||
unclipped_left,
|
||||
right;
|
||||
|
||||
public ColumnGeometry(ColumnBase column, DIYGridSuperHeader super_column, double left, double right)
|
||||
: this(column, super_column, left, left, right) { }
|
||||
|
||||
public ColumnGeometry(ColumnBase column, DIYGridSuperHeader super_column, double left, double unclipped_left, double right) {
|
||||
this.column = column;
|
||||
this.super_column = super_column;
|
||||
this.left = left;
|
||||
this.unclipped_left = left;
|
||||
this.right = right;
|
||||
}
|
||||
|
||||
public double width => right - left;
|
||||
public bool is_partially_obscured { get { return unclipped_left < left; } }
|
||||
public double visible_width => Math.Max(0, width - left + unclipped_left);
|
||||
public override string ToString() {
|
||||
return "{3} {0:0.0},{1:0.0} w{2:0.0}".fmt(left, right, width, column.Header);
|
||||
}
|
||||
public string group { get { return super_column == null ? "" : super_column.FullHeader; } }
|
||||
|
||||
public RectangleGeometry get_clip(double row_height) =>
|
||||
new RectangleGeometry(new Rect(left, 0, width - left + unclipped_left, row_height));
|
||||
public Rect to_rect(double row_height) =>
|
||||
new Rect(left, 0, width, row_height);
|
||||
|
||||
public IDisposable clip_draw(DrawingContext dc, double row_height) {
|
||||
dc.PushClip(get_clip(row_height));
|
||||
return Disposable.Create(dc.Pop);
|
||||
public override string ToString() {
|
||||
return "{3} {0:0.0},{1:0.0} w{2:0.0}".fmt(left, right, width, column.Header);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+2
-4
@@ -1,9 +1,6 @@
|
||||
using mbts.spark.filters;
|
||||
using mbts.ui.converters;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Data;
|
||||
using System.Windows.Media;
|
||||
|
||||
namespace mbts.ui.controls {
|
||||
partial class DIYGrid {
|
||||
@@ -29,7 +26,8 @@ namespace mbts.ui.controls {
|
||||
hl.SetBinding(HyperlinkCell.TextProperty, _binding);
|
||||
hl.SetBinding(HyperlinkCell.FontFamilyProperty, new Binding(nameof(font_family)) { Source = this });
|
||||
hl.SetBinding(HyperlinkCell.FontSizeProperty, new Binding(nameof(font_size)) { Source = this });
|
||||
hl.SetBinding(HyperlinkCell.HorizontalAlignmentProperty, new Binding(nameof(horizontal_alignment)) { Source = this });
|
||||
hl.SetBinding(HyperlinkCell.FontWeightProperty, new Binding(nameof(font_weight)) { Source = this });
|
||||
hl.SetBinding(HyperlinkCell.HorizontalContentAlignmentProperty, new Binding(nameof(horizontal_alignment)) { Source = this });
|
||||
hl.SetBinding(HyperlinkCell.CommandProperty, _command_binding);
|
||||
hl.SetBinding(HyperlinkCell.WidthProperty, new Binding(nameof(ActualWidth)) { Source = this });
|
||||
hl.SetBinding(HyperlinkCell.BackgroundProperty, new Binding(nameof(background_brush)) { Source = this });
|
||||
|
||||
+7
-3
@@ -18,12 +18,15 @@ namespace mbts.ui.controls {
|
||||
public IDataGridColumnFilter filter { get; }
|
||||
public virtual string filter_template => DataGridFilterKnownTemplates.AlgoType;
|
||||
public BindingBase font_weight_binding { get; set; }
|
||||
public Thickness padding { get; set; } = DefaultCellPadding;
|
||||
|
||||
public override FrameworkElement view_factory() {
|
||||
var v = new OrderTypeItem {
|
||||
FontFamily = font_family,
|
||||
VerticalAlignment = VerticalAlignment.Center,
|
||||
VerticalContentAlignment = VerticalAlignment.Center
|
||||
HorizontalAlignment = HorizontalAlignment.Stretch,
|
||||
VerticalAlignment = VerticalAlignment.Stretch,
|
||||
VerticalContentAlignment = VerticalAlignment.Center,
|
||||
Padding = padding
|
||||
};
|
||||
v.SetBinding(OrderTypeItem.BadgeFillProperty, _badge_fill_binding);
|
||||
v.SetBinding(OrderTypeItem.DisplayNameProperty, _display_name_binding);
|
||||
@@ -32,10 +35,11 @@ namespace mbts.ui.controls {
|
||||
} else {
|
||||
bind(FontWeightProperty, nameof(font_weight));
|
||||
}
|
||||
bind(BackgroundProperty, nameof(background_brush));
|
||||
bind(FontFamilyProperty, nameof(font_family));
|
||||
bind(FontSizeProperty, nameof(font_size));
|
||||
bind(ForegroundProperty, nameof(font_brush));
|
||||
bind(HorizontalAlignmentProperty, nameof(horizontal_alignment));
|
||||
bind(HorizontalContentAlignmentProperty, nameof(horizontal_alignment));
|
||||
bind(OrderTypeItem.WidthProperty, nameof(ActualWidth));
|
||||
return v;
|
||||
//
|
||||
|
||||
@@ -11,6 +11,7 @@ namespace mbts.ui.controls {
|
||||
this.filter = filter;
|
||||
this.filter_template = filter_template;
|
||||
_binding = binding;
|
||||
background_binding = new Binding(nameof(background_brush)) { Source = this };
|
||||
}
|
||||
|
||||
public IDataGridColumnFilter filter { get; }
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
using bts.utils;
|
||||
using mbts.spark.filters;
|
||||
using mbts.ui.controls.adorners;
|
||||
using mbts.ui.dpi;
|
||||
using mbts.ui.extensions;
|
||||
using mbts.ui.settings;
|
||||
using mbts.ui.utility;
|
||||
@@ -442,8 +443,12 @@ namespace mbts.ui.controls {
|
||||
|
||||
static DIYGrid() {
|
||||
DefaultStyleKeyProperty.OverrideMetadata(typeof(DIYGrid), new FrameworkPropertyMetadata(typeof(DIYGrid)));
|
||||
UseLayoutRoundingProperty.OverrideMetadata(typeof(DIYGrid), new FrameworkPropertyMetadata(OnUseLayoutRoundingChanged));
|
||||
DefaultSelectionHighlight.Freeze();
|
||||
DefaultSelectionStroke.Freeze();
|
||||
|
||||
void OnUseLayoutRoundingChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) =>
|
||||
d.CoerceValue(RowHeightProperty);
|
||||
}
|
||||
|
||||
public DIYGrid() {
|
||||
@@ -1069,7 +1074,7 @@ namespace mbts.ui.controls {
|
||||
if (v < min_row_height)
|
||||
return min_row_height;
|
||||
}
|
||||
return v;
|
||||
return o.UseLayoutRounding ? v.RoundLayoutValue() : v;
|
||||
}, DefaultRowHeight);
|
||||
|
||||
public event Action<double> RowHeightChanged;
|
||||
@@ -1146,33 +1151,30 @@ namespace mbts.ui.controls {
|
||||
double frozen_offset = 0;
|
||||
int vis_frozen_col_count = _get_visible_frozen_column_count();
|
||||
foreach (var col in _visible_columns.Take(vis_frozen_col_count)) {
|
||||
yield return new ColumnGeometry() {
|
||||
column = col,
|
||||
left = frozen_offset,
|
||||
unclipped_left = frozen_offset,
|
||||
};
|
||||
double right = frozen_offset + col.ActualWidth;
|
||||
right = right.RoundLayoutValue();
|
||||
yield return new ColumnGeometry(col, null, frozen_offset, frozen_offset, right);
|
||||
frozen_offset += col.ActualWidth;
|
||||
}
|
||||
// frozen_offset will be off by 2 pixels because of the GridViewHeaderRowPresenter PaddingHeader (see c_PaddingHeaderMinWidth in referencesource)
|
||||
// We could use the _header_frozen.ActualWidth but due to the async nature of layout it get updated slightly too late
|
||||
frozen_offset += 2;
|
||||
if (_frozen_column_count > 0) {
|
||||
// frozen_offset will be off by 2 pixels because of the GridViewHeaderRowPresenter PaddingHeader (see c_PaddingHeaderMinWidth in referencesource)
|
||||
// We could use the _header_frozen.ActualWidth but due to the async nature of layout it get updated slightly too late
|
||||
frozen_offset += 2;
|
||||
}
|
||||
// Adjust by scroll amount
|
||||
double offset = frozen_offset - (_scroll_horizontal.Value);
|
||||
double offset = frozen_offset - _scroll_horizontal.Value;
|
||||
offset = offset.RoundLayoutValue();
|
||||
int count = 0;
|
||||
foreach (var col in _visible_columns.Skip(vis_frozen_col_count)) {
|
||||
double left = Math.Max(offset, frozen_offset);
|
||||
double right = offset + col.ActualWidth;
|
||||
right = right.RoundLayoutValue();
|
||||
// If right < frozen_offset column is completely obscured
|
||||
if (include_obscured_cols || (right > frozen_offset && left <= ActualWidth)) {
|
||||
// fully visible
|
||||
yield return new ColumnGeometry() {
|
||||
column = col,
|
||||
super_column = _superheader_column_index.ElementAtOrDefault(count),
|
||||
unclipped_left = offset,
|
||||
left = left
|
||||
};
|
||||
yield return new ColumnGeometry(col, _superheader_column_index.ElementAtOrDefault(count), left, offset, right);
|
||||
}
|
||||
offset += col.ActualWidth;
|
||||
offset = right;
|
||||
++count;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -55,6 +55,7 @@
|
||||
<Setter Property="BorderThickness" Value="0"/>
|
||||
<Setter Property="Focusable" Value="False"/>
|
||||
<Setter Property="SelectionMode" Value="Extended"/>
|
||||
<Setter Property="UseLayoutRounding" Value="True"/>
|
||||
<Setter Property="ColumnHeaderStyle">
|
||||
<Setter.Value>
|
||||
<Style TargetType="GridViewColumnHeader">
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using bts.utils;
|
||||
using mbts.ui.dpi;
|
||||
using mbts.ui.extensions;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
@@ -123,7 +124,7 @@ namespace mbts.ui.controls {
|
||||
protected override Size ArrangeOverride(Size finalSize) {
|
||||
foreach (var kv in _column_layout_map) {
|
||||
if (_element_cells.TryGetValue(kv.Key, out FrameworkElement ec))
|
||||
ec.Arrange(new Rect(new Point(kv.Value.left, 0), new Size(kv.Value.width, _row_height)));
|
||||
ec.Arrange(kv.Value.to_rect(_row_height));
|
||||
}
|
||||
return finalSize;
|
||||
}
|
||||
|
||||
@@ -53,11 +53,7 @@ namespace mbts.ui.controls {
|
||||
if (col.is_partially_obscured) {
|
||||
var obscured_width = col.left - col.unclipped_left;
|
||||
grid.scroll_horizontal(-obscured_width);
|
||||
return new ColumnGeometry() {
|
||||
column = col.column,
|
||||
left = col.left,
|
||||
unclipped_left = col.left
|
||||
};
|
||||
return new ColumnGeometry(col.column, col.super_column, col.left, col.left, col.right);
|
||||
}
|
||||
return col;
|
||||
}
|
||||
|
||||
@@ -1,18 +1,11 @@
|
||||
using bts.collections;
|
||||
using bts.proto;
|
||||
using mbts.collections;
|
||||
using bts.utils;
|
||||
using mbts.ui;
|
||||
using mbts.ui.controls;
|
||||
using mbts.ui.settings;
|
||||
using mbts.ui.shell;
|
||||
using mbts.ui.utility;
|
||||
using mbts.ui.viewmodels;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reactive.Linq;
|
||||
using System.Windows;
|
||||
using System.Windows.Media;
|
||||
using CellAnimation = mbts.ui.CellAnimation;
|
||||
|
||||
namespace mbts.spark {
|
||||
/// <summary>
|
||||
@@ -28,172 +21,110 @@ namespace mbts.spark {
|
||||
ImmutableFontColor = 1,
|
||||
ImmutableFontWeight = 1 << 1,
|
||||
ImmutableHeader = 1 << 2,
|
||||
ImmutableBackgroundColor = 1 << 3
|
||||
ImmutableBackgroundColor = 1 << 3,
|
||||
ImmutableVisibility = 1 << 4
|
||||
}
|
||||
|
||||
partial class GridSettingsVM<TRowData, TPredicateData, TSettingsData> {
|
||||
public interface IColumnVM : ISimpleColumnVM, IValueFactory<(GridColumnSettings settings, ILookup<TPredicateData, CellAnimation> animations)>, IDisposable {
|
||||
ObservableEditableCollection<PredicateCellAnimationVM> predicate_animations { get; }
|
||||
new (GridColumnSettings settings, ILookup<TPredicateData, CellAnimation> animations) create();
|
||||
new void load_from((GridColumnSettings settings, ILookup<TPredicateData, CellAnimation> animations) args);
|
||||
class GridColumnSettingsVM : ParameterGroupVM {
|
||||
string _id = string.Empty;
|
||||
GridColumnSettings _settings;
|
||||
|
||||
public GridColumnSettingsVM()
|
||||
: this(GridColumnSettingsFeatureFlags.None) { }
|
||||
|
||||
protected GridColumnSettingsVM(GridColumnSettingsFeatureFlags? init_flags) : base("") {
|
||||
if (font_color.override_value.A == 0)
|
||||
font_color.set_override(font_color.override_value.with(255));
|
||||
if (init_flags.HasValue)
|
||||
apply_flags(init_flags.Value);
|
||||
}
|
||||
|
||||
public class ColumnVM : DisposableViewModelBase, IColumnVM {
|
||||
public readonly GridSettingsVM<TRowData, TPredicateData, TSettingsData> owner;
|
||||
public string id { get => _id; private set => Set(ref _id, value); }
|
||||
public StringOverrideParameterVM header { get; } = new StringOverrideParameterVM("Header");
|
||||
public BooleanOverrideParameterVM show { get; } = new BooleanOverrideParameterVM("Show");
|
||||
public DoubleParameterVM width { get; } = new DoubleParameterVM("Width");
|
||||
public ColorOverrideParameterVM background { get; } = new ColorOverrideParameterVM("Background");
|
||||
public ColorOverrideParameterVM font_color { get; } = new ColorOverrideParameterVM("Font Color");
|
||||
public FontWeightOverrideParameterVM font_weight { get; } = new FontWeightOverrideParameterVM();
|
||||
public HorizontalAlignmentParameterVM horizontal_alignment { get; } = new HorizontalAlignmentParameterVM("Horizontal Alignment");
|
||||
|
||||
string
|
||||
_id = string.Empty,
|
||||
_display_name = string.Empty;
|
||||
double _width = 70;
|
||||
HorizontalAlignment _horizontal_alignment = HorizontalAlignment.Left;
|
||||
bool
|
||||
_is_visible,
|
||||
_can_override_display_name = true,
|
||||
_can_override_font_color = true,
|
||||
_is_font_color_overridden,
|
||||
_can_override_font_weight = true,
|
||||
_is_font_weight_overridden,
|
||||
_can_override_background_color = true,
|
||||
_is_background_color_overridden;
|
||||
FontWeight _font_weight_override;
|
||||
GridColumnSettings _settings;
|
||||
|
||||
public ColumnVM(GridSettingsVM<TRowData, TPredicateData, TSettingsData> owner) {
|
||||
this.owner = owner;
|
||||
// animations
|
||||
predicate_animations = new ObservableEditableCollection<PredicateCellAnimationVM>(() =>
|
||||
new PredicateCellAnimationVM(id, owner._create_column_predicate_vm(id), owner._predicate_data_factory), true);
|
||||
predicate_animations.CollectionChanged += WPFHelper.create_collection_changed_cb(on_items_added: (_, __) => owner.show_cell_predicate_animations = true);
|
||||
_dispose = Disposable.Create(() => predicate_animations.OfType<IDisposable>().DisposeAll());
|
||||
}
|
||||
|
||||
public string id { get => _id; private set => Set(ref _id, value); }
|
||||
public double width { get => _width; set => Set(ref _width, value); }
|
||||
public HorizontalAlignment horizontal_alignment { get => _horizontal_alignment; set => Set(ref _horizontal_alignment, value); }
|
||||
public bool is_visible { get => _is_visible; set => Set(ref _is_visible, value); }
|
||||
// display name
|
||||
public bool can_override_display_name { get => _can_override_display_name; private set => Set(ref _can_override_display_name, value); }
|
||||
public string display_name { get => _display_name; set => Set(ref _display_name, value); }
|
||||
// font color override
|
||||
public bool can_override_font_color { get => _can_override_font_color; private set => Set(ref _can_override_font_color, value); }
|
||||
public bool is_font_color_overridden { get => _is_font_color_overridden; set => Set(ref _is_font_color_overridden, value); }
|
||||
public ColorPickerPopupVM font_color_override { get; } = new ColorPickerPopupVM();
|
||||
// font weight override
|
||||
public bool can_override_font_weight { get => _can_override_font_weight; private set => Set(ref _can_override_font_weight, value); }
|
||||
public bool is_font_weight_overridden { get => _is_font_weight_overridden; set => Set(ref _is_font_weight_overridden, value); }
|
||||
public FontWeight font_weight_override { get => _font_weight_override; set => Set(ref _font_weight_override, value); }
|
||||
// background color override
|
||||
public bool can_override_background_color { get => _can_override_background_color; private set => Set(ref _can_override_background_color, value); }
|
||||
public bool is_background_color_overridden { get => _is_background_color_overridden; set => Set(ref _is_background_color_overridden, value); }
|
||||
public ColorPickerPopupVM background_color_override { get; } = new ColorPickerPopupVM() { bind_opacity_to_alpha = true };
|
||||
// animations
|
||||
public ObservableEditableCollection<PredicateCellAnimationVM> predicate_animations { get; }
|
||||
|
||||
public (GridColumnSettings settings, ILookup<TPredicateData, CellAnimation> animations) create() {
|
||||
var settings = _settings.with(
|
||||
can_override_display_name ? display_name : null,
|
||||
is_visible,
|
||||
null,
|
||||
is_font_weight_overridden ? new NullableFontWeightProto(font_weight_override) : new NullableFontWeightProto(null),
|
||||
is_font_color_overridden ? new NullableColorProto(font_color_override.color) : new NullableColorProto(null),
|
||||
width,
|
||||
horizontal_alignment,
|
||||
background_color_override: is_background_color_overridden ?
|
||||
new NullableColorProto(background_color_override.color) : default);
|
||||
var animations_lookup = predicate_animations.ToFlatLookup(pa => pa.predicate.create_assigned(owner._predicate_data_factory), pa => pa.animations.Select(a => a.create()));
|
||||
return (settings, animations_lookup);
|
||||
}
|
||||
|
||||
public void load_from((GridColumnSettings settings, ILookup<TPredicateData, CellAnimation> animations) value) {
|
||||
_settings = value.settings;
|
||||
id = _settings.id;
|
||||
display_name = _settings.display_name;
|
||||
width = _settings.width;
|
||||
horizontal_alignment = _settings.horizontal_alignment;
|
||||
is_visible = _settings.is_visible;
|
||||
// font color
|
||||
is_font_color_overridden = _settings.font_color_override.HasValue;
|
||||
if (is_font_color_overridden) {
|
||||
font_color_override.load_from(new SolidColorBrush(_settings.font_color_override.Value));
|
||||
}
|
||||
// font weight
|
||||
is_font_weight_overridden = _settings.font_weight_override.HasValue;
|
||||
if (is_font_weight_overridden) {
|
||||
_font_weight_override = _settings.font_weight_override.Value;
|
||||
}
|
||||
// background color
|
||||
is_background_color_overridden = _settings.background_color_override.HasValue;
|
||||
if (is_background_color_overridden) {
|
||||
background_color_override.color = _settings.background_color_override.Value;
|
||||
}
|
||||
predicate_animations.SetItems(value.animations.empty_if_null().Select(g => {
|
||||
var vm = new PredicateCellAnimationVM(id, owner._create_column_predicate_vm(id), owner._predicate_data_factory);
|
||||
vm.load_from((g.Key, g));
|
||||
return vm;
|
||||
}));
|
||||
}
|
||||
|
||||
public void apply_flags(GridColumnSettingsFeatureFlags flags) {
|
||||
// display name
|
||||
can_override_display_name = (flags & GridColumnSettingsFeatureFlags.ImmutableHeader) == 0;
|
||||
if (!can_override_display_name)
|
||||
display_name = id;
|
||||
// font color
|
||||
can_override_font_color = (flags & GridColumnSettingsFeatureFlags.ImmutableFontColor) == 0;
|
||||
is_font_color_overridden &= can_override_font_color;
|
||||
// font weight
|
||||
can_override_font_weight = (flags & GridColumnSettingsFeatureFlags.ImmutableFontWeight) == 0;
|
||||
is_font_weight_overridden &= can_override_font_weight;
|
||||
// background color
|
||||
can_override_background_color = (flags & GridColumnSettingsFeatureFlags.ImmutableBackgroundColor) == 0;
|
||||
is_background_color_overridden &= can_override_background_color;
|
||||
}
|
||||
|
||||
public override string ToString() => $"{display_name} ({id})";
|
||||
|
||||
GridColumnSettings IValueFactory<GridColumnSettings>.create() =>
|
||||
create().settings;
|
||||
|
||||
void IValueFactory<GridColumnSettings>.load_from(GridColumnSettings value) =>
|
||||
load_from((value, new Dictionary<TPredicateData, CellAnimation>().ToLookup(kv => kv.Key, kv => kv.Value)));
|
||||
public GridColumnSettings create() {
|
||||
return _settings.with(
|
||||
header.effective_is_overridden ? header.override_value : id,
|
||||
show.effective_value,
|
||||
null,
|
||||
_get_optional_font_weight_proto(font_weight),
|
||||
_get_optional_color_proto(font_color),
|
||||
width.value,
|
||||
horizontal_alignment.value,
|
||||
null,
|
||||
_get_optional_color_proto(background));
|
||||
}
|
||||
|
||||
public class PredicateCellAnimationVM : DisposableViewModelBase, IValueFactory<(TPredicateData predicate, IEnumerable<CellAnimation> animations)> {
|
||||
//
|
||||
readonly string _col_id;
|
||||
readonly Property<bool> _show_animations;
|
||||
readonly Func<TPredicateData> _predicate_data_factory;
|
||||
|
||||
public PredicateCellAnimationVM(string col_id, IPropertyWrapperVM<TPredicateData> predicate, Func<TPredicateData> predicate_data_factory) {
|
||||
_col_id = col_id;
|
||||
this.predicate = predicate;
|
||||
_predicate_data_factory = predicate_data_factory;
|
||||
_show_animations = backing(nameof(show_animations), true);
|
||||
animations = new ObservableEditableCollection<CellAnimationVM>(() => {
|
||||
var vm = new CellAnimationVM(col_id);
|
||||
vm.selected_animation_type = vm.animation_types.FirstOrDefault();
|
||||
return vm;
|
||||
}, true);
|
||||
animations.CollectionChanged += WPFHelper.create_collection_changed_cb(on_items_added: (_, __) => _show_animations.set(true));
|
||||
_dispose = predicate as IDisposable;
|
||||
}
|
||||
|
||||
public IPropertyWrapperVM<TPredicateData> predicate { get; }
|
||||
public ObservableEditableCollection<CellAnimationVM> animations { get; }
|
||||
public IEnumerable<CellAnimation.AnimationTypeOneofCase> animation_types { get; } = EnumHelper.get_values_except(CellAnimation.AnimationTypeOneofCase.None);
|
||||
public bool show_animations { get => _show_animations.Value; set => _show_animations.set(value); }
|
||||
|
||||
public (TPredicateData predicate, IEnumerable<CellAnimation> animations) create() =>
|
||||
(predicate.create_assigned(_predicate_data_factory), animations.Select(a => a.create()));
|
||||
|
||||
public void load_from((TPredicateData predicate, IEnumerable<CellAnimation> animations) args) {
|
||||
predicate.load_from(args.predicate);
|
||||
animations.SetItems(args.animations.Select(a => {
|
||||
var vm = new CellAnimationVM(_col_id);
|
||||
vm.load_from(a);
|
||||
return vm;
|
||||
}));
|
||||
public void load_from(GridColumnSettings value) {
|
||||
_settings = value;
|
||||
id = _settings.id;
|
||||
header.default_value = _settings.id;
|
||||
header.override_value = _settings.display_name;
|
||||
width.value = _settings.width;
|
||||
horizontal_alignment.value = _settings.horizontal_alignment;
|
||||
show.override_value = value.is_visible;
|
||||
// font color
|
||||
font_color.is_overridden = _settings.font_color_override.HasValue;
|
||||
if (font_color.can_override) {
|
||||
if (font_color.is_overridden)
|
||||
font_color.override_value = _settings.font_color_override.Value;
|
||||
}
|
||||
// font weight
|
||||
font_weight.is_overridden = _settings.font_weight_override.HasValue;
|
||||
if (font_weight.is_overridden)
|
||||
font_weight.override_value = _settings.font_weight_override.Value;
|
||||
// background
|
||||
background.is_overridden = _settings.background_color_override.HasValue;
|
||||
if (background.is_overridden)
|
||||
background.override_value = _settings.background_color_override.Value;
|
||||
}
|
||||
|
||||
public void apply_flags(GridColumnSettingsFeatureFlags flags) {
|
||||
header.can_override = (flags & GridColumnSettingsFeatureFlags.ImmutableHeader) == 0;
|
||||
show.can_override = (flags & GridColumnSettingsFeatureFlags.ImmutableVisibility) == 0;
|
||||
font_weight.can_override = (flags & GridColumnSettingsFeatureFlags.ImmutableFontWeight) == 0;
|
||||
font_color.can_override = (flags & GridColumnSettingsFeatureFlags.ImmutableFontColor) == 0;
|
||||
background.can_override = (flags & GridColumnSettingsFeatureFlags.ImmutableBackgroundColor) == 0;
|
||||
sync_params();
|
||||
}
|
||||
|
||||
public virtual void sync_params() {
|
||||
var layout = new HeaderedParameterGroupVM("Column Layout");
|
||||
var fonts_and_colors = new HeaderedParameterGroupVM("Fonts/Colors");
|
||||
if (header.can_override)
|
||||
layout.parameters.Add(header);
|
||||
layout.parameters.Add(width);
|
||||
layout.parameters.Add(horizontal_alignment);
|
||||
if (font_weight.can_override)
|
||||
fonts_and_colors.parameters.Add(font_weight);
|
||||
if (font_color.can_override)
|
||||
fonts_and_colors.parameters.Add(font_color);
|
||||
if (background.can_override)
|
||||
fonts_and_colors.parameters.Add(background);
|
||||
parameters.SetItems(layout, fonts_and_colors);
|
||||
}
|
||||
|
||||
public override string ToString() =>
|
||||
header.ToString();
|
||||
|
||||
static NullableColorProto? _get_optional_color_proto(ColorOverrideParameterVM optional_color) =>
|
||||
optional_color.can_override ?
|
||||
optional_color.is_overridden ?
|
||||
new NullableColorProto(optional_color.override_value) :
|
||||
new NullableColorProto() :
|
||||
(NullableColorProto?)null;
|
||||
|
||||
static NullableFontWeightProto? _get_optional_font_weight_proto(ParameterOverrideVM<FontWeight> optional_font_weight) =>
|
||||
optional_font_weight.can_override ?
|
||||
optional_font_weight.is_overridden ?
|
||||
new NullableFontWeightProto(optional_font_weight.override_value) :
|
||||
new NullableFontWeightProto() :
|
||||
(NullableFontWeightProto?)null;
|
||||
}
|
||||
}
|
||||
@@ -16,7 +16,7 @@ namespace mbts.ui.settings {
|
||||
const string _default_font_family = "Segoe UI";
|
||||
const double _default_font_size = 11;
|
||||
const uint
|
||||
_default_font_color = 0,
|
||||
_default_font_color = 0xFF000000,
|
||||
_default_background = 0x00FFFFFF;
|
||||
const ushort _default_font_weight = 400;
|
||||
const uint _default_background_color = 0x00FFFFFF; // Colors.Transparent
|
||||
|
||||
@@ -1,12 +1,8 @@
|
||||
using bts;
|
||||
using bts.api;
|
||||
using bts.collections;
|
||||
using bts.shell;
|
||||
using bts.utils;
|
||||
using mbts.spark;
|
||||
using mbts.ui.settings;
|
||||
using mbts.ui.shell;
|
||||
using mbts.utils;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
@@ -16,73 +12,31 @@ using System.Windows.Media;
|
||||
using static mbts.ui.controls.DIYGrid;
|
||||
|
||||
namespace mbts.ui {
|
||||
class GridSettingsManager<TRow, TRowData, TPredicateData, TSettingsData> :
|
||||
ViewModelBase, IValueFactory<TSettingsData>, IObserver<TSettingsData>, IDisposable
|
||||
where TRow : IRowVM
|
||||
where TSettingsData : class, IGridSettingsWithNotifications<TRowData, TPredicateData> {
|
||||
class GridSettingsManager : ViewModelBase {
|
||||
//
|
||||
readonly IBus _bus;
|
||||
readonly OrdersModel _orders_model;
|
||||
readonly List<IDisposable> _notification_subs = new List<IDisposable>();
|
||||
readonly ObservableCollection<ColumnBase> _columns;
|
||||
readonly IEnumerable<TRow> _rows;
|
||||
readonly Property<GridSettings> _grid_settings;
|
||||
readonly DelegateProperty<FontFamily> _font_family;
|
||||
readonly DelegateProperty<FontWeight> _font_weight;
|
||||
readonly DelegateProperty<double> _font_size;
|
||||
readonly DelegateProperty<SolidColorBrush> _font_color, _selection_highlight, _background_color;
|
||||
readonly SetMap<IPredicate<TRowData>, AnimationDefinitionWrapper<TRow>> _animation_defs = new SetMap<IPredicate<TRowData>, AnimationDefinitionWrapper<TRow>>();
|
||||
readonly Func<TPredicateData> _predicate_data_factory;
|
||||
readonly Func<TPredicateData, IPredicate<TRowData>> _predicate_factory;
|
||||
readonly Func<TPredicateData, INotificationPredicate<TRowData>> _notification_predicate_factory;
|
||||
readonly Func<IPropertyWrapperVM<TPredicateData>> _create_predicate_vm;
|
||||
readonly Func<string, IPropertyWrapperVM<TPredicateData>> _create_column_predicate_vm;
|
||||
readonly Func<GridSettings, IEnumerable<IPredicateAnimationsData<TRowData, TPredicateData>>, TSettingsData> _create_settings_obj;
|
||||
readonly Func<IPredicate<TRowData>, SoundNotification, IDisposable> _link_sound;
|
||||
readonly Func<INotificationPredicate<TRowData>, IDisposable> _link_toast_notification;
|
||||
readonly IReadOnlyDictionary<string, ImmutableColumnSettingStore> _column_flags;
|
||||
IEnumerable<IPredicateAnimationsData<TRowData, TPredicateData>> _predicate_animations_data;
|
||||
|
||||
public GridSettingsManager(
|
||||
IBus bus,
|
||||
OrdersModel orders_model,
|
||||
IEnumerable<TRow> rows,
|
||||
ObservableCollection<ColumnBase> columns,
|
||||
Func<TPredicateData> predicate_data_factory,
|
||||
Func<IPropertyWrapperVM<TPredicateData>> create_predicate_vm,
|
||||
Func<string, IPropertyWrapperVM<TPredicateData>> create_column_predicate_vm,
|
||||
Func<GridSettings, IEnumerable<IPredicateAnimationsData<TRowData, TPredicateData>>, TSettingsData> create_settings_obj,
|
||||
Func<TPredicateData, IPredicate<TRowData>> predicate_factory,
|
||||
Func<TPredicateData, INotificationPredicate<TRowData>> notification_predicate_factory,
|
||||
Func<IPredicate<TRowData>, SoundNotification, IDisposable> link_sound,
|
||||
Func<INotificationPredicate<TRowData>, IDisposable> link_toast_notification,
|
||||
IReadOnlyDictionary<string, GridColumnSettingsFeatureFlags> column_flags = null) {
|
||||
//
|
||||
_bus = bus;
|
||||
_orders_model = orders_model;
|
||||
_rows = rows;
|
||||
_columns = columns;
|
||||
_predicate_data_factory = predicate_data_factory;
|
||||
_predicate_factory = predicate_factory;
|
||||
_notification_predicate_factory = notification_predicate_factory;
|
||||
_create_predicate_vm = create_predicate_vm;
|
||||
_create_column_predicate_vm = create_column_predicate_vm;
|
||||
_create_settings_obj = create_settings_obj;
|
||||
_link_sound = link_sound;
|
||||
_link_toast_notification = link_toast_notification;
|
||||
_column_flags = column_flags.ToDictionary(kv => kv.Key, kv => ImmutableColumnSettingStore.create(kv.Value));
|
||||
_column_flags = column_flags?.ToDictionary(kv => kv.Key, kv => ImmutableColumnSettingStore.create(kv.Value));
|
||||
_grid_settings = backing(nameof(grid_settings), GridSettings.Default);
|
||||
_font_family = backing(nameof(font_family), () => new FontFamily(grid_settings.font_family), nameof(grid_settings));
|
||||
_font_weight = backing(nameof(font_weight), () => grid_settings.font_weight, nameof(grid_settings));
|
||||
_font_size = backing(nameof(font_size), () => grid_settings.font_size, nameof(font_size));
|
||||
_font_color = backing(nameof(font_color), () => grid_settings.font_color.rgb_solid_brush().frozen(), nameof(grid_settings));
|
||||
_selection_highlight = backing(nameof(selection_highlight), () => grid_settings.selection_highlight.to_brush().frozen(), nameof(grid_settings));
|
||||
_selection_highlight = backing(nameof(selection_highlight), () => grid_settings.selection_highlight.to_argb_brush().frozen(), nameof(grid_settings));
|
||||
_background_color = backing(nameof(background_color), () => grid_settings.background_color.argb_color().to_solid_brush().frozen(), nameof(grid_settings));
|
||||
}
|
||||
|
||||
public void Dispose() =>
|
||||
Disposable.dispose(_notification_subs);
|
||||
|
||||
public GridSettings grid_settings => _grid_settings.Value;
|
||||
public FontFamily font_family => _font_family.Value;
|
||||
public FontWeight font_weight => _font_weight.Value;
|
||||
@@ -91,52 +45,25 @@ namespace mbts.ui {
|
||||
public SolidColorBrush selection_highlight => _selection_highlight.Value;
|
||||
public SolidColorBrush background_color => _background_color.Value;
|
||||
|
||||
public GridSettingsVM<TRowData, TPredicateData, TSettingsData> create_vm(GridSettings settings) {
|
||||
var vm = new GridSettingsVM<TRowData, TPredicateData, TSettingsData>(_bus, _orders_model, _predicate_data_factory, _notification_predicate_factory, _create_predicate_vm, _create_column_predicate_vm, _create_settings_obj, _column_flags.ToDictionary(kv => kv.Key, kv => kv.Value.flags));
|
||||
vm.load(_create_settings_obj(settings, _predicate_animations_data));
|
||||
return vm;
|
||||
public GridSettings create() {
|
||||
try {
|
||||
_grid_settings.set(grid_settings.with(columns: _columns.Select(c => c.create())));
|
||||
} catch (Exception ex) {
|
||||
Log.Ui.warn($"Error refreshing grid settings (may be benign, if caused by accessing a ColumnBase property after window closed): {ex}.");
|
||||
}
|
||||
return grid_settings;
|
||||
}
|
||||
|
||||
public TSettingsData create() {
|
||||
_grid_settings.set(grid_settings.with(_columns.Select(c => {
|
||||
var col_settings = c.create();
|
||||
if (_column_flags.TryGetValue(c.id, out ImmutableColumnSettingStore store))
|
||||
col_settings = store.apply(col_settings);
|
||||
return col_settings;
|
||||
})));
|
||||
return _create_settings_obj(grid_settings, _predicate_animations_data);
|
||||
}
|
||||
public GridSettingsVM create_vm() =>
|
||||
new GridSettingsVM(_column_flags?.ToDictionary(kv => kv.Key, kv => kv.Value.flags));
|
||||
|
||||
public void load_from(TSettingsData value) => _load_from_core(value, true);
|
||||
|
||||
void _load_from_core(TSettingsData value, bool reset_immutables) {
|
||||
Disposable.dispose(_notification_subs);
|
||||
var grid = value.general ?? GridSettings.Default;
|
||||
public void load_from(GridSettings grid) {
|
||||
_grid_settings.set(grid);
|
||||
foreach (ColumnBase col in _columns)
|
||||
col.set_fallback_font_params(new FontFamily(grid.font_family), grid.font_size, grid.font_color.rgb_color(), grid.font_weight, grid.background_color.argb_color());
|
||||
_predicate_animations_data = value.predicate_animations;
|
||||
_animation_defs.clear();
|
||||
foreach (var pa in _predicate_animations_data) {
|
||||
var predicate = _predicate_factory(pa.predicate_data);
|
||||
if (predicate == null)
|
||||
continue;
|
||||
foreach (var s in pa.sound_notifications) {
|
||||
_notification_subs.Add(_link_sound(predicate, s));
|
||||
}
|
||||
var notification_predicate = _notification_predicate_factory(pa.predicate_data);
|
||||
if (notification_predicate != null)
|
||||
_notification_subs.Add(_link_toast_notification(notification_predicate));
|
||||
}
|
||||
// ** in the unfortunate event that we botch backwards compatability, and the columns don't bear a 1:1 relationship with the column-settings
|
||||
// don't add insult to injury by crashing **
|
||||
if (grid_settings.columns != null && _columns != null) {
|
||||
if (reset_immutables) {
|
||||
foreach (var kv in _column_flags) {
|
||||
if (grid.columns.TryGetFirst(c => c.id == kv.Key, out GridColumnSettings cs))
|
||||
kv.Value.store(cs);
|
||||
}
|
||||
}
|
||||
var col_settings_id_map = grid_settings.columns.ToDictionary(cs => id_key(cs.id));
|
||||
var columns_id_map = _columns.ToDictionary(c => id_key(c.id));
|
||||
if (col_settings_id_map.Count == columns_id_map.Count && col_settings_id_map.Count == grid_settings.columns.Count) {
|
||||
@@ -150,31 +77,8 @@ namespace mbts.ui {
|
||||
col.load_from(cs);
|
||||
}
|
||||
// invalidate all the rows to draw them again to reflect the latest settings
|
||||
foreach (var r in _rows) r.raise_invalidated();
|
||||
//
|
||||
string id_key(string id) => id.ToLower();
|
||||
}
|
||||
}
|
||||
|
||||
void IObserver<TSettingsData>.OnNext(TSettingsData sd) {
|
||||
if (sd == null) return;
|
||||
try {
|
||||
var grid = sd.general;
|
||||
if (grid.columns != null) {
|
||||
var cols_with_applied_flags = grid.columns.ToList();
|
||||
for (int i = 0; i < cols_with_applied_flags.Count; i++) {
|
||||
if (_column_flags.TryGetValue(cols_with_applied_flags[i].id, out ImmutableColumnSettingStore store)) {
|
||||
cols_with_applied_flags[i] = store.apply(cols_with_applied_flags[i]);
|
||||
}
|
||||
}
|
||||
sd = _create_settings_obj(grid.with(cols_with_applied_flags), sd.predicate_animations);
|
||||
}
|
||||
_load_from_core(sd, false);
|
||||
} catch (Exception ex) {
|
||||
Log.Ui.error($"Error loading GridSettings (as observer): {ex}");
|
||||
}
|
||||
}
|
||||
void IObserver<TSettingsData>.OnError(Exception error) { }
|
||||
void IObserver<TSettingsData>.OnCompleted() { }
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,17 @@
|
||||
using mbts.ui.converters;
|
||||
using System.Windows;
|
||||
using System.Windows.Data;
|
||||
|
||||
namespace mbts.ui.spark {
|
||||
/// <summary>
|
||||
/// Interaction logic for GridSettingsView.xaml
|
||||
/// </summary>
|
||||
partial class GridSettingsResources : ResourceDictionary {
|
||||
public GridSettingsResources() =>
|
||||
InitializeComponent();
|
||||
|
||||
public static IMultiValueConverter ColumnDisplayNameVisibilityConverter =>
|
||||
ValueConverter.create<bool, string, string, Visibility>((show_display_name, id, header) =>
|
||||
!show_display_name || id == header ? Visibility.Collapsed : Visibility.Visible);
|
||||
}
|
||||
}
|
||||
@@ -1,23 +1,20 @@
|
||||
using bts.api;
|
||||
using bts.collections;
|
||||
using bts.collections;
|
||||
using bts.proto;
|
||||
using bts.shell;
|
||||
using bts.utils;
|
||||
using mbts.collections;
|
||||
using mbts.ui;
|
||||
using mbts.ui.controls;
|
||||
using mbts.ui.settings;
|
||||
using mbts.ui.shell;
|
||||
using mbts.ui.utility;
|
||||
using mbts.ui.utility.text;
|
||||
using mbts.ui.viewmodels;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
using System.Reactive.Linq;
|
||||
using System.Windows;
|
||||
using CellAnimation = mbts.ui.CellAnimation;
|
||||
using FontFamily = System.Windows.Media.FontFamily;
|
||||
using System.Windows.Media;
|
||||
|
||||
namespace mbts.spark {
|
||||
interface IGridSettingsWithNotifications<TRowData, TPredicateData> {
|
||||
@@ -45,70 +42,65 @@ namespace mbts.spark {
|
||||
EqualityComparer.safe_aggregate_hash(predicate_animations_data));
|
||||
}
|
||||
|
||||
partial class GridSettingsVM<TRowData, TPredicateData, TSettingsData> : DisposableViewModelBase, IRequestViewClose, IWindowSettingsVM<TSettingsData>, ITemplateSelectorKey
|
||||
where TSettingsData :class, IGridSettingsWithNotifications<TRowData, TPredicateData> {
|
||||
//
|
||||
protected readonly Func<IPropertyWrapperVM<TPredicateData>> _create_predicate_vm;
|
||||
protected readonly Func<string, IPropertyWrapperVM<TPredicateData>> _create_column_predicate_vm;
|
||||
readonly Func<TPredicateData> _predicate_data_factory;
|
||||
readonly Func<GridSettings, IEnumerable<IPredicateAnimationsData<TRowData, TPredicateData>>, TSettingsData> _create_settings_obj;
|
||||
readonly Property<IColumnVM> _current_column;
|
||||
readonly Property<FontFamily> _font_family;
|
||||
readonly Property<FontWeight> _font_weight;
|
||||
readonly Property<string> _font_display_name;
|
||||
readonly Property<double> _font_size;
|
||||
readonly Property<bool> _show_predicate_animations, _show_cell_predicate_animations, _show_background_color;
|
||||
readonly OrdersModel _orders_model;
|
||||
class GridSettingsVM : ParameterGroupVM, IRequestViewClose, IWindowSettingsVM<GridSettings>, ITemplateSelectorKey {
|
||||
readonly FontParameterVM _font_vm = new FontParameterVM("Cell Font");
|
||||
readonly ColorParameterVM
|
||||
_font_color = new ColorParameterVM("Cell Font Color"),
|
||||
_selection_highlight = new ColorParameterVM("Selection Highlight"),
|
||||
_background = new ColorParameterVM("Background");
|
||||
readonly Property<GridColumnSettingsVM> _current_column;
|
||||
readonly IReadOnlyDictionary<string, GridColumnSettingsFeatureFlags> _column_flags;
|
||||
bool _background_enabled, _show_column_display_name;
|
||||
IDisposable _dispose;
|
||||
|
||||
public GridSettingsVM(
|
||||
IBus bus,
|
||||
OrdersModel orders_model,
|
||||
Func<TPredicateData> predicate_data_factory,
|
||||
Func<TPredicateData, INotificationPredicate<TRowData>> notification_predicate_factory,
|
||||
Func<IPropertyWrapperVM<TPredicateData>> create_predicate_vm,
|
||||
Func<string, IPropertyWrapperVM<TPredicateData>> create_column_predicate_vm,
|
||||
Func<GridSettings, IEnumerable<IPredicateAnimationsData<TRowData, TPredicateData>>, TSettingsData> create_settings_obj,
|
||||
IReadOnlyDictionary<string, GridColumnSettingsFeatureFlags> column_flags = null) {
|
||||
//
|
||||
this.bus = bus;
|
||||
_orders_model = orders_model;
|
||||
_predicate_data_factory = predicate_data_factory;
|
||||
_create_predicate_vm = create_predicate_vm;
|
||||
_create_column_predicate_vm = create_column_predicate_vm;
|
||||
_create_settings_obj = create_settings_obj;
|
||||
protected GridSettingsVM(string name)
|
||||
: base(name) { }
|
||||
|
||||
public GridSettingsVM(IReadOnlyDictionary<string, GridColumnSettingsFeatureFlags> column_flags = null)
|
||||
: this("Grid Settings") {
|
||||
_column_flags = column_flags ?? new Dictionary<string, GridColumnSettingsFeatureFlags>();
|
||||
_font_family = backing(nameof(font_family), TextUtils.StandardFontFamily);
|
||||
_font_size = backing(nameof(font_size), FontSizes.Small);
|
||||
_font_weight = backing(nameof(font_weight), FontWeights.Normal);
|
||||
_font_display_name = backing(nameof(font_display_name), () => TextUtils.display_name(font_family, font_size), new[] { nameof(font_family), nameof(font_size) });
|
||||
_show_predicate_animations = backing(nameof(show_predicate_animations), true);
|
||||
_show_cell_predicate_animations = backing(nameof(show_cell_predicate_animations), true);
|
||||
_show_background_color = backing(nameof(show_background_color), false);
|
||||
_current_column = backing<IColumnVM>(nameof(current_column));
|
||||
predicate_animations = new ObservableEditableCollection<PredicateRowAnimationVM>(() =>
|
||||
new PredicateRowAnimationVM(_create_predicate_vm(), _predicate_data_factory), true);
|
||||
predicate_animations.CollectionChanged += WPFHelper.create_collection_changed_cb(on_items_added: (_, __) => _show_predicate_animations.set(true));
|
||||
_current_column = backing<GridColumnSettingsVM>(nameof(current_column));
|
||||
sync_params();
|
||||
// font
|
||||
_dispose = Disposable.Create(ObservableAutoWrapper.connect_item_hooks(columns, c => {
|
||||
_font_vm.result_changed += on_result_changed;
|
||||
_background.result_changed += on_result_changed;
|
||||
_font_color.Changed += on_font_color_changed;
|
||||
on_font_color_changed(default, _font_color.value);
|
||||
on_result_changed();
|
||||
return Disposable.Create(() => {
|
||||
_font_vm.result_changed -= on_result_changed;
|
||||
_background.result_changed -= on_result_changed;
|
||||
_font_color.Changed -= on_font_color_changed;
|
||||
});
|
||||
// set the whole state everytime, bc it's simpler and less error prone
|
||||
// than coordinating between specific properties.
|
||||
void on_result_changed() {
|
||||
c.background.default_value = _background.value;
|
||||
c.font_weight.family = _font_vm.family;
|
||||
c.font_weight.default_value = _font_vm.weight;
|
||||
c.font_weight.size = _font_vm.size;
|
||||
}
|
||||
void on_font_color_changed(Color oldval, Color newval) =>
|
||||
c.font_color.default_value = newval;
|
||||
}));
|
||||
}
|
||||
|
||||
public event EventHandler RequestClose;
|
||||
public event Action committed;
|
||||
|
||||
public IBus bus { get; }
|
||||
public ObservableCollection<IColumnVM> columns { get; } = new ObservableCollection<IColumnVM>();
|
||||
public IColumnVM current_column { get => _current_column.Value; set => _current_column.set(value); }
|
||||
public string font_display_name => _font_display_name.Value;
|
||||
public FontFamily font_family { get => _font_family.Value; set => _font_family.set(value); }
|
||||
public FontWeight font_weight { get => _font_weight.Value; set => _font_weight.set(value); }
|
||||
public ColorPickerPopupVM font_color { get; } = new ColorPickerPopupVM();
|
||||
public ColorPickerPopupVM selection_highlight { get; } = new ColorPickerPopupVM();
|
||||
public ColorPickerPopupVM background_color { get; } = new ColorPickerPopupVM();
|
||||
public bool show_background_color => _show_background_color.Value;
|
||||
public double font_size { get => _font_size.Value; set => _font_size.set(value); }
|
||||
public bool enable_sound_triggers => true;
|
||||
public bool show_predicate_animations { get => _show_predicate_animations.Value; set => _show_predicate_animations.set(value); }
|
||||
public bool show_cell_predicate_animations { get => _show_cell_predicate_animations.Value; set => _show_cell_predicate_animations.set(value); }
|
||||
public ObservableEditableCollection<PredicateRowAnimationVM> predicate_animations { get; }
|
||||
public ObservableCollection<GridColumnSettingsVM> columns { get; } = new ObservableCollection<GridColumnSettingsVM>();
|
||||
|
||||
public GridColumnSettingsVM current_column {
|
||||
get => _current_column.Value;
|
||||
set => _current_column.set(value);
|
||||
}
|
||||
|
||||
public bool show_column_display_name {
|
||||
get => _show_column_display_name;
|
||||
set => Set(ref _show_column_display_name, value);
|
||||
}
|
||||
|
||||
public IReadOnlyList<DialogSkirtItem> dialog_skirt_items => new[] {
|
||||
new DialogSkirtItem("OK", new DelegateCommand(() => {
|
||||
committed?.Invoke();
|
||||
@@ -120,116 +112,82 @@ namespace mbts.spark {
|
||||
|
||||
public string key => ui.spark.GridSettingsTemplateKeys.SimpleGrid;
|
||||
|
||||
public TSettingsData get_result() {
|
||||
var animation_map = predicate_animations
|
||||
.Select(p => p.create())
|
||||
.GroupBy(d => d.predicate)
|
||||
.ToDictionary(g => g.Key, g => animation_tuple(
|
||||
g.SelectMany(v => v.animations).ToList(),
|
||||
g.SelectMany(v => v.sounds).ToList()));
|
||||
public IDisposable begin_change_columns() {
|
||||
var curr_col_id = current_column?.id;
|
||||
return Disposable.Create(() => {
|
||||
if (curr_col_id == null || !columns.TryGetFirst(c => c.id == curr_col_id, out var curr_column))
|
||||
curr_column = columns.FirstOrDefault();
|
||||
_current_column.set(curr_column);
|
||||
});
|
||||
}
|
||||
|
||||
public GridSettings get_result() {
|
||||
var col_settings = new List<GridColumnSettings>();
|
||||
foreach (var c in columns) {
|
||||
var (settings, animations) = c.create();
|
||||
var settings = c.create();
|
||||
col_settings.Add(settings);
|
||||
foreach (IGrouping<TPredicateData, CellAnimation> g in animations)
|
||||
animation_map.AddOrUpdate(g.Key, p => animation_tuple(c: g), (p, t) => t.cell_animations.AddRange(g));
|
||||
}
|
||||
return _create_settings_obj(new GridSettings(font_family.name(), font_size, font_weight, font_color.color.rgb_color(), selection_highlight.to_proto(), col_settings,
|
||||
background: background_color.color.with((byte)(MathUtils.within_range(0, 1, background_color.opacity) * 255.0)).rgb_color()),
|
||||
animation_map.Select(kv => PredicateAnimations.create_data<TRowData, TPredicateData>(kv.Key, kv.Value.cell_animations, kv.Value.row_animations, kv.Value.sounds)));
|
||||
//
|
||||
(List<RowAnimation> row_animations, List<SoundNotification> sounds, List<CellAnimation> cell_animations) animation_tuple(IEnumerable<RowAnimation> r = null, IEnumerable<SoundNotification> s = null, IGrouping<TPredicateData, CellAnimation> c = null) =>
|
||||
(r?.ToList() ?? new List<RowAnimation>(), s?.ToList() ?? new List<SoundNotification>(), c?.ToList() ?? new List<CellAnimation>());
|
||||
return new GridSettings(
|
||||
_font_vm.family?.name(),
|
||||
_font_vm.size,
|
||||
_font_vm.weight,
|
||||
_font_color.value.rgb_color(),
|
||||
new ColorOpacityProto(_selection_highlight.value.rgb_color()),
|
||||
col_settings,
|
||||
background: _background_enabled ? _background.value.rgb_color() : 0,
|
||||
enable_background_color: _background_enabled);
|
||||
}
|
||||
|
||||
public void load(TSettingsData sd) {
|
||||
_font_family.set(new FontFamily(sd.general.font_family));
|
||||
_font_weight.set(sd.general.font_weight);
|
||||
font_color.load_from(sd.general.font_color.rgb_solid_brush());
|
||||
selection_highlight.load_from(sd.general.selection_highlight.to_brush());
|
||||
_show_background_color.set(sd.general.enable_background_color);
|
||||
if (sd.general.enable_background_color) {
|
||||
// note that when the OB is moved to allow background colors
|
||||
// we can just background_color.color instead of load_from
|
||||
// so that we get all the alpha goodness similar to SimpleGridSettingsVM
|
||||
// assuming bind_opacity_to_alpha on the picker.
|
||||
background_color.load_from(sd.general.background_color.argb_color().to_solid_brush());
|
||||
}
|
||||
_font_size.set(sd.general.font_size);
|
||||
var col_animations = new Dictionary<string, Dictionary<TPredicateData, List<CellAnimation>>>();
|
||||
predicate_animations.Clear();
|
||||
foreach (var pa in sd.predicate_animations) {
|
||||
if (pa.row_animations.Count > 0 || pa.sound_notifications.Count > 0) {
|
||||
var vm = new PredicateRowAnimationVM(_create_predicate_vm(), _predicate_data_factory);
|
||||
vm.load_from((pa.predicate_data, pa.row_animations, pa.sound_notifications));
|
||||
predicate_animations.Add(vm);
|
||||
}
|
||||
foreach (var ca in pa.cell_animations) {
|
||||
col_animations.AddOrUpdate(ca.column_id,
|
||||
col => new Dictionary<TPredicateData, List<CellAnimation>> { { pa.predicate_data, new List<CellAnimation> { ca } } },
|
||||
(col, d) => d.AddOrUpdate(pa.predicate_data, p => new List<CellAnimation> { ca }, (p, l) => l.Add(ca)));
|
||||
public void load(GridSettings settings) =>
|
||||
load(settings, true);
|
||||
|
||||
public void load(GridSettings settings, bool sync_columns) {
|
||||
_font_vm.family = new FontFamily(settings.font_family);
|
||||
_font_vm.weight = settings.font_weight;
|
||||
_font_vm.size = settings.font_size;
|
||||
_font_color.value = settings.font_color.rgb_color();
|
||||
_selection_highlight.value = settings.selection_highlight.argb_color;
|
||||
// note that when the OB is moved to allow background colors
|
||||
// we can just background_color.color instead of load_from
|
||||
// so that we get all the alpha goodness similar to SimpleGridSettingsVM
|
||||
// assuming bind_opacity_to_alpha on the picker.
|
||||
_background_enabled = settings.enable_background_color;
|
||||
_background.value = settings.background;
|
||||
if (sync_columns) {
|
||||
using (begin_change_columns()) {
|
||||
columns.SetItems(settings.columns.Select(col => {
|
||||
var vm = new GridColumnSettingsVM();
|
||||
vm.load_from(col);
|
||||
// reapply our favorite column flags
|
||||
// this comes in handy when performing Load From Factory - the editor will contain
|
||||
// a consistent view of what can be overridden and what cannot
|
||||
if (_column_flags.TryGetValue(vm.id, out GridColumnSettingsFeatureFlags flags))
|
||||
vm.apply_flags(flags);
|
||||
return vm;
|
||||
}));
|
||||
}
|
||||
}
|
||||
var curr_col_id = current_column?.id;
|
||||
columns.SetItems(sd.general.columns.Select(col => {
|
||||
var vm = new ColumnVM(this);
|
||||
vm.load_from((col, col_animations.GetValueOrDefault(col.id)?.ToFlatLookup(kv => kv.Key, kv => kv.Value)));
|
||||
// reapply our favorite column flags
|
||||
// this comes in handy when performing Load From Factory - the editor will contain
|
||||
// a consistent view of what can be overridden and what cannot
|
||||
if (_column_flags.TryGetValue(vm.id, out GridColumnSettingsFeatureFlags flags))
|
||||
vm.apply_flags(flags);
|
||||
return vm;
|
||||
}));
|
||||
if (curr_col_id == null || !columns.TryGetFirst(c => c.id == curr_col_id, out IColumnVM curr_column))
|
||||
curr_column = columns.FirstOrDefault();
|
||||
_current_column.set(curr_column);
|
||||
sync_params();
|
||||
}
|
||||
|
||||
public class PredicateRowAnimationVM : DisposableViewModelBase, IValueFactory<(TPredicateData predicate, IEnumerable<RowAnimation> animations, IEnumerable<SoundNotification> sounds)> {
|
||||
readonly Property<bool> _show_animations/*, _play_sound*/;
|
||||
readonly Func<TPredicateData> _predicate_state_factory;
|
||||
public IObservable<Color> subscribe_background() =>
|
||||
_background.SubscribeValue(true);
|
||||
|
||||
public PredicateRowAnimationVM(IPropertyWrapperVM<TPredicateData> predicate, Func<TPredicateData> predicate_state_factory) {
|
||||
this.predicate = predicate;
|
||||
_predicate_state_factory = predicate_state_factory;
|
||||
_show_animations = backing(nameof(show_animations), true);
|
||||
//_play_sound = backing(nameof(play_sound), true);
|
||||
animations = new ObservableEditableCollection<RowAnimationVM>(() => {
|
||||
var vm = new RowAnimationVM();
|
||||
vm.selected_animation_type = vm.animation_types.FirstOrDefault();
|
||||
return vm;
|
||||
}, true);
|
||||
animations.CollectionChanged += WPFHelper.create_collection_changed_cb(on_items_added: (_, __) => _show_animations.set(true));
|
||||
_dispose = predicate as IDisposable;
|
||||
}
|
||||
public IObservable<Color> subscribe_font_color() =>
|
||||
_font_color.SubscribeValue(true);
|
||||
|
||||
public IPropertyWrapperVM<TPredicateData> predicate { get; }
|
||||
public ObservableEditableCollection<RowAnimationVM> animations { get; }
|
||||
public IEnumerable<CellAnimation.AnimationTypeOneofCase> animation_types { get; } = EnumHelper.get_values_except(CellAnimation.AnimationTypeOneofCase.None);
|
||||
public bool show_animations { get => _show_animations.Value; set => _show_animations.set(value); }
|
||||
//public bool play_sound { get => _play_sound.Value; set => _play_sound.set(value); }
|
||||
public IPropertyWrapperVM<SoundNotification> sound { get; } = SoundAnimationVM.create_system_sound_vm();
|
||||
public IObservable<(FontFamily family, FontWeight weight, double size)> subscribe_font() =>
|
||||
_font_vm.SubscribeValue(true);
|
||||
|
||||
public (TPredicateData predicate, IEnumerable<RowAnimation> animations, IEnumerable<SoundNotification> sounds) create() =>
|
||||
(predicate.create_assigned(_predicate_state_factory),
|
||||
animations.Select(a => a.create_assigned()),
|
||||
new[] { sound.create_assigned() });
|
||||
//EnumerableExtensions.return_if(play_sound, () => sound.create_assigned()));
|
||||
|
||||
public void load_from((TPredicateData predicate, IEnumerable<RowAnimation> animations, IEnumerable<SoundNotification> sounds) args) {
|
||||
predicate.load_from(args.predicate);
|
||||
animations.SetItems(args.animations.Select(a => {
|
||||
var vm = new RowAnimationVM();
|
||||
vm.load_from(a);
|
||||
return vm;
|
||||
}));
|
||||
var en = args.sounds.GetEnumerator();
|
||||
if (en.MoveNext()) {
|
||||
//_play_sound.set(true);
|
||||
sound.load_from(en.Current);
|
||||
} //else _play_sound.set(false);
|
||||
}
|
||||
public virtual void sync_params() {
|
||||
var fonts_and_colors = HeaderedParameterGroupVM.create("Fonts/Colors", _font_vm, _font_color);
|
||||
if (_background_enabled)
|
||||
fonts_and_colors.parameters.Add(_background);
|
||||
fonts_and_colors.parameters.Add(_selection_highlight);
|
||||
parameters.SetItems(fonts_and_colors);
|
||||
}
|
||||
|
||||
public void Dispose() =>
|
||||
Disposable.dispose(ref _dispose);
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large.
Load diff
@@ -49,6 +49,8 @@ namespace mbts.ui.controls
|
||||
};
|
||||
}
|
||||
|
||||
public bool SelectItemOnDrop { get; set; }
|
||||
|
||||
private DragIndicatorAdorner<DraggableListBoxItem> _adorner;
|
||||
private IDisposable _drag_operation;
|
||||
|
||||
@@ -56,9 +58,13 @@ namespace mbts.ui.controls
|
||||
|
||||
protected override DependencyObject GetContainerForItemOverride() {
|
||||
var container = new DraggableListBoxItem();
|
||||
container.MouseEnter += (s, e) => _adorner.InvalidateVisual();
|
||||
container.MouseLeave += (s, e) => _adorner.InvalidateVisual();
|
||||
container.MouseEnter += on_mouse_changed;
|
||||
container.MouseLeave += on_mouse_changed;
|
||||
container.MouseMove += on_mouse_changed;
|
||||
return container;
|
||||
//
|
||||
void on_mouse_changed(object sender, MouseEventArgs e) =>
|
||||
_adorner.InvalidateVisual();
|
||||
}
|
||||
|
||||
protected override void OnPreviewMouseLeftButtonDown(MouseButtonEventArgs e) {
|
||||
@@ -89,6 +95,8 @@ namespace mbts.ui.controls
|
||||
if (source != null && target != null) {
|
||||
CoerceValue(DragDropManagerProperty);
|
||||
DragDropManager.Drop(source, target.Content);
|
||||
if (SelectItemOnDrop && target is ListBoxItem lbi)
|
||||
lbi.SetCurrentValue(ListBoxItem.IsSelectedProperty, true);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
using mbts.ui.utility;
|
||||
using bts.utils;
|
||||
using mbts.ui.utility;
|
||||
using mbts.ui.utility.text;
|
||||
using bts.utils;
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Globalization;
|
||||
@@ -9,7 +9,6 @@ using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Data;
|
||||
using System.Windows.Media;
|
||||
using FontFamily = System.Windows.Media.FontFamily;
|
||||
|
||||
namespace mbts.ui.controls {
|
||||
/// <summary>
|
||||
@@ -20,13 +19,8 @@ namespace mbts.ui.controls {
|
||||
|
||||
public FontPicker() {
|
||||
InitializeComponent();
|
||||
var families = Fonts.SystemFontFamilies.ToList();
|
||||
families.Sort(CompareUtils.safe_comparison<FontFamily>(
|
||||
(a, b) => a.FamilyNames.First().Value.CompareTo(b.FamilyNames.First().Value)));
|
||||
SetCurrentValue(FontFamiliesProperty, families);
|
||||
var weights = TextUtils.all_font_weights().ToList();
|
||||
weights.Sort((x, y) => x.ToOpenTypeWeight().CompareTo(y.ToOpenTypeWeight()));
|
||||
SetCurrentValue(FontWeightsProperty, weights);
|
||||
SetCurrentValue(FontFamiliesProperty, TextUtils.SystemFontFamilies);
|
||||
SetCurrentValue(FontWeightsProperty, TextUtils.SystemFontWeights);
|
||||
Loaded += (s, e) => _font_family_list_box.ScrollIntoView(SelectedFontFamily);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:c="clr-namespace:mbts.ui.controls"
|
||||
xmlns:usp="clr-namespace:mbts.ui.spark"
|
||||
xmlns:uspcv="clr-namespace:mbts.ui.spark.converters">
|
||||
<ResourceDictionary
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:c="clr-namespace:mbts.ui.controls"
|
||||
xmlns:usp="clr-namespace:mbts.ui.spark"
|
||||
xmlns:uspcv="clr-namespace:mbts.ui.spark.converters"
|
||||
>
|
||||
<uspcv:OrderTypeBrushConverter x:Key="OrderTypeBrushConverter"/>
|
||||
<uspcv:OrderTypeDisplayTextConverter x:Key="OrderTypeDisplayTextConverter"/>
|
||||
<Style TargetType="usp:OrderTypeItem">
|
||||
@@ -26,15 +28,17 @@
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<c:Badge
|
||||
<Rectangle
|
||||
HorizontalAlignment="Left"
|
||||
VerticalAlignment="Center"
|
||||
Background="{Binding BadgeFill, RelativeSource={RelativeSource TemplatedParent}}"
|
||||
BorderThickness="1"
|
||||
Fill="{Binding BadgeFill, RelativeSource={RelativeSource TemplatedParent}}"
|
||||
Stroke="Black"
|
||||
StrokeThickness="1"
|
||||
Width="15"
|
||||
Height="10"
|
||||
Focusable="False"
|
||||
IsTabStop="False"
|
||||
RadiusX="{x:Static c:Badge.DefaultCornerRadius}"
|
||||
RadiusY="{x:Static c:Badge.DefaultCornerRadius}"
|
||||
/>
|
||||
<TextBlock
|
||||
x:Name="text"
|
||||
|
||||
@@ -10,9 +10,18 @@ namespace mbts.ui.controls {
|
||||
/// Interaction logic for PopupControl.xaml
|
||||
/// </summary>
|
||||
public class PopupControl : HeaderedContentControl {
|
||||
IDisposable _popup_hook;
|
||||
Popup _popup;
|
||||
Button _button;
|
||||
|
||||
static PopupControl() =>
|
||||
DefaultStyleKeyProperty.OverrideMetadata(typeof(PopupControl), new FrameworkPropertyMetadata(typeof(PopupControl)));
|
||||
|
||||
public PopupControl() {
|
||||
CommandBindings.Add(new CommandBinding(OpenCommand, (s, e) => IsOpen = true));
|
||||
CommandBindings.Add(new CommandBinding(CloseCommand, (s, e) => IsOpen = false));
|
||||
}
|
||||
|
||||
public static readonly RoutedCommand
|
||||
OpenCommand = new RoutedCommand("Open", typeof(PopupControl)),
|
||||
CloseCommand = new RoutedCommand("Close", typeof(PopupControl));
|
||||
@@ -23,6 +32,16 @@ namespace mbts.ui.controls {
|
||||
public event RoutedEventHandler Opened { add => AddHandler(OpenedEvent, value); remove => RemoveHandler(OpenedEvent, value); }
|
||||
public event RoutedEventHandler Closed { add => AddHandler(ClosedEvent, value); remove => RemoveHandler(ClosedEvent, value); }
|
||||
|
||||
public static readonly DependencyProperty ButtonStyleProperty =
|
||||
WPFHelper.create_dp<Style, PopupControl>(
|
||||
nameof(ButtonStyle),
|
||||
(o, v) => WPFHelper.apply_style(ref o._button, v));
|
||||
|
||||
public Style ButtonStyle {
|
||||
get => GetValue(ButtonStyleProperty) as Style;
|
||||
set => SetValue(ButtonStyleProperty, value);
|
||||
}
|
||||
|
||||
#region PopupPlacement
|
||||
public static readonly DependencyProperty PopupPlacementProperty = DependencyProperty.Register("PopupPlacement", typeof(PlacementMode), typeof(PopupControl), new PropertyMetadata(PlacementMode.Right, OnPopupPlacementChanged));
|
||||
private static void OnPopupPlacementChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { d.SetValue(IsOpenTransformPropertyKey, _get_is_open_transform((PlacementMode)e.NewValue)); }
|
||||
@@ -105,18 +124,12 @@ namespace mbts.ui.controls {
|
||||
}
|
||||
#endregion
|
||||
|
||||
public PopupControl() {
|
||||
CommandBindings.Add(new CommandBinding(OpenCommand, (s, e) => IsOpen = true));
|
||||
CommandBindings.Add(new CommandBinding(CloseCommand, (s, e) => IsOpen = false));
|
||||
}
|
||||
|
||||
IDisposable _popup_hook;
|
||||
Popup _popup;
|
||||
Button _button;
|
||||
|
||||
public override void OnApplyTemplate() {
|
||||
Disposable.dispose(ref _popup_hook);
|
||||
// button
|
||||
_button = (Button)GetTemplateChild("button");
|
||||
WPFHelper.apply_style(ref _button, ButtonStyle);
|
||||
// popup
|
||||
_popup = (Popup)GetTemplateChild("popup");
|
||||
_popup.HorizontalOffset = HorizontalOffset;
|
||||
_popup.VerticalOffset = VerticalOffset;
|
||||
|
||||
@@ -1,9 +1,29 @@
|
||||
using System;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls.Primitives;
|
||||
using System.Windows.Input;
|
||||
|
||||
namespace mbts.ui.controls {
|
||||
static class PopupViewController {
|
||||
#region ButtonSource
|
||||
public static readonly DependencyProperty ButtonSourceProperty =
|
||||
DependencyProperty.RegisterAttached("ButtonSource", typeof(ButtonBase), typeof(PopupViewController),
|
||||
new PropertyMetadata(OnButtonSourceChanged));
|
||||
|
||||
static void OnButtonSourceChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) {
|
||||
if (d is Popup popup)
|
||||
popup.SetValue(ButtonSourceDependencyPropertyKey,
|
||||
e.NewValue is ButtonBase button ? attach(popup, button) : null);
|
||||
}
|
||||
|
||||
public static bool GetButtonSource(DependencyObject d) => (bool)d.GetValue(ButtonSourceProperty);
|
||||
public static void SetButtonSource(DependencyObject d, bool value) => d.SetValue(ButtonSourceProperty, value);
|
||||
|
||||
static readonly DependencyPropertyKey ButtonSourceDependencyPropertyKey =
|
||||
DependencyProperty.RegisterAttachedReadOnly("ButtonSourceDependency", typeof(IDisposable), typeof(PopupViewController),
|
||||
new PropertyMetadata(FrameworkElementLifeDependencyHelper.DisposeOld));
|
||||
#endregion
|
||||
|
||||
public static IDisposable attach(Popup popup, ButtonBase open_cmd_source, bool auto_focus_popup_root_on_open = false) {
|
||||
/// There is a paradoxical corner case handled here that occurs when the user clicks the button to 'close' the popup.
|
||||
/// Conventional logic might dictate that a <see cref="ToggleButton "/> be used to bind <see cref="ToggleButton.IsChecked"/> to <see cref="Popup.IsOpen"/>,
|
||||
|
||||
@@ -55,6 +55,7 @@
|
||||
<SolidColorBrush Color="{StaticResource MouseOverBorderLightColor}"/>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
<Setter Property="Background" Value="{u:BTSColor UI.Button.mouseover_background}"/>
|
||||
</Trigger>
|
||||
<Trigger Property="IsEnabled" Value="False">
|
||||
<Setter Property="Opacity" Value="0.5"/>
|
||||
@@ -62,6 +63,67 @@
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
|
||||
<Style x:Key="SparkToolBarButton" TargetType="Button">
|
||||
<Setter Property="ToolTipService.ShowOnDisabled" Value="True"/>
|
||||
<Setter Property="BorderThickness" Value="1"/>
|
||||
<Setter Property="Height" Value="22"/>
|
||||
<Setter Property="HorizontalContentAlignment" Value="Stretch"/>
|
||||
<Setter Property="Padding" Value="2"/>
|
||||
<Setter Property="VerticalContentAlignment" Value="Stretch"/>
|
||||
<Setter Property="Width" Value="22"/>
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="Button">
|
||||
<Border
|
||||
x:Name="Border"
|
||||
BorderThickness="{TemplateBinding BorderThickness}"
|
||||
Padding="{TemplateBinding Padding}"
|
||||
Height="{TemplateBinding Height}"
|
||||
Width="{TemplateBinding Width}"
|
||||
SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"
|
||||
Opacity="1"
|
||||
>
|
||||
<Border.BorderBrush>
|
||||
<SolidColorBrush/>
|
||||
</Border.BorderBrush>
|
||||
<Border.Background>
|
||||
<SolidColorBrush/>
|
||||
</Border.Background>
|
||||
<VisualStateManager.VisualStateGroups>
|
||||
<VisualStateGroup Name="CommonStates">
|
||||
<VisualState Name="Normal"/>
|
||||
<VisualState Name="MouseOver">
|
||||
<Storyboard>
|
||||
<ColorAnimation Storyboard.TargetProperty="Background.Color" BeginTime="0" Duration="0" To="{DynamicResource Control.AlphaLightMediumGrayColor}"/>
|
||||
<ColorAnimation Storyboard.TargetProperty="BorderBrush.Color" BeginTime="0" Duration="0" To="{DynamicResource Control.DarkGrayColor}"/>
|
||||
</Storyboard>
|
||||
</VisualState>
|
||||
<VisualState u:StateManager.Name="Pressed">
|
||||
<Storyboard>
|
||||
<ColorAnimation Storyboard.TargetProperty="Background.Color" BeginTime="0" Duration="0" To="{DynamicResource Control.AlphaMediumGrayColor}"/>
|
||||
<ColorAnimation Storyboard.TargetProperty="BorderBrush.Color" BeginTime="0" Duration="0" To="{DynamicResource Control.DarkGrayColor}"/>
|
||||
</Storyboard>
|
||||
</VisualState>
|
||||
<VisualState Name="Disabled">
|
||||
<Storyboard>
|
||||
<DoubleAnimation Storyboard.TargetProperty="Opacity" BeginTime="0" Duration="0" To="0.52"/>
|
||||
</Storyboard>
|
||||
</VisualState>
|
||||
</VisualStateGroup>
|
||||
</VisualStateManager.VisualStateGroups>
|
||||
<ContentPresenter
|
||||
Name="presenter"
|
||||
Grid.Column="1"
|
||||
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
|
||||
VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
|
||||
RecognizesAccessKey="True"
|
||||
/>
|
||||
</Border>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
|
||||
<Style x:Key="ToolBarThumbStyle" TargetType="{x:Type Thumb}">
|
||||
<Setter Property="OverridesDefaultStyle" Value="True"/>
|
||||
<Setter Property="Cursor" Value="SizeAll"/>
|
||||
@@ -161,7 +223,7 @@
|
||||
<Setter Property="HorizontalAlignment" Value="Stretch"/>
|
||||
<Setter Property="SnapsToDevicePixels" Value="True"/>
|
||||
<Setter Property="OverridesDefaultStyle" Value="True"/>
|
||||
<Setter Property="ItemContainerStyle" Value="{StaticResource SparkToolBarItem}"/>
|
||||
<!--<Setter Property="ItemContainerStyle" Value="{StaticResource SparkToolBarItem}"/>-->
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="{x:Type ToolBar}">
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
using bts.spark;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Windows.Data;
|
||||
using System.Windows.Media;
|
||||
|
||||
namespace mbts.ui.converters {
|
||||
static class ValueConverter {
|
||||
@@ -25,8 +28,12 @@ namespace mbts.ui.converters {
|
||||
public static readonly IValueConverter
|
||||
DoubleTextConverter = new DoubleTextConverter(),
|
||||
DoubleGreaterThanZero = new DoubleGreaterThanConverter { Value = 0 },
|
||||
GeometryParser = new TypeConverterWrapper {
|
||||
TypeConverter = new GeometryConverter()
|
||||
},
|
||||
IntTextConverter = new IntTextConverter(),
|
||||
UIntTextConverter = new UIntTextConverter(),
|
||||
NotNull = ValueConverter.create(obj => obj != null),
|
||||
QuantityMeasureTextConverter = ValueConverter.create<QuantityMeasureImmutable?, string>(fmt),
|
||||
SecuritySelectorText,
|
||||
SimpleString = new AnonymousValueConverter(obj => obj?.ToString()),
|
||||
@@ -40,7 +47,12 @@ namespace mbts.ui.converters {
|
||||
}
|
||||
|
||||
public static readonly IMultiValueConverter
|
||||
AreEqualConverter = new AreEqualConverter();
|
||||
AreEqualConverter = new AreEqualConverter(),
|
||||
SliderPercentage = ValueConverter.create<double, double, double, double>((value, min, max) =>
|
||||
DoubleHelper.AnyNaN(value, min, max) || min >= max ?
|
||||
double.NaN :
|
||||
(value - min) / (max - min)),
|
||||
Any = new AnonymousMultiValueConverter(objs => objs.OfTypeSafe<bool>().Any(_ => _), null);
|
||||
|
||||
public static string fmt(QuantityMeasureImmutable? qm) {
|
||||
if (qm.HasValue) {
|
||||
@@ -80,6 +92,22 @@ namespace mbts.ui.converters {
|
||||
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) { return _convert_back(ConvertUtils.try_convert<TOut>(value)); }
|
||||
}
|
||||
|
||||
class AnonymousMultiValueConverter : IMultiValueConverter {
|
||||
readonly Func<object[], object> _convert;
|
||||
readonly Func<object, object[]> _convert_back;
|
||||
|
||||
public AnonymousMultiValueConverter(Func<object[], object> convert, Func<object, object[]> convert_back) {
|
||||
_convert = convert;
|
||||
_convert_back = convert_back;
|
||||
}
|
||||
|
||||
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture) =>
|
||||
_convert(values);
|
||||
|
||||
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture) =>
|
||||
_convert_back == null ? throw new InvalidOperationException() : _convert_back(value);
|
||||
}
|
||||
|
||||
public class AnonymousMultiValueConverter<TIn1, TIn2, TOut> : IMultiValueConverter {
|
||||
readonly Func<TIn1, TIn2, TOut> _convert;
|
||||
|
||||
|
||||
@@ -2,9 +2,10 @@
|
||||
using System.Globalization;
|
||||
using System.Text;
|
||||
using System.Windows.Data;
|
||||
using System.Windows.Markup;
|
||||
|
||||
namespace mbts.ui.converters {
|
||||
class AppendTextConverter : IValueConverter {
|
||||
class AppendTextConverter : MarkupExtension, IValueConverter {
|
||||
public string Prefix { get; set; }
|
||||
public string Suffix { get; set; }
|
||||
|
||||
@@ -18,5 +19,8 @@ namespace mbts.ui.converters {
|
||||
|
||||
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) =>
|
||||
throw new NotImplementedException();
|
||||
|
||||
public override object ProvideValue(IServiceProvider serviceProvider) =>
|
||||
this;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
using mbts.utils;
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Data;
|
||||
|
||||
namespace mbts.ui.converters {
|
||||
class ComparisonConverter : IValueConverter {
|
||||
public object ValueForGreaterThan { get; set; } = 1;
|
||||
public object ValueForLessThan { get; set; } = -1;
|
||||
public object ValueForEqual { get; set; }
|
||||
public object Comparand { get; set; }
|
||||
public IComparer Comparer { get; set; }
|
||||
|
||||
public object Convert(object value, Type targetType, object parameter, CultureInfo culture) =>
|
||||
convert(value, Comparand, Comparer, ValueForLessThan, ValueForGreaterThan, ValueForEqual);
|
||||
|
||||
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) =>
|
||||
convert(value, Comparand, Comparer, ValueForGreaterThan, ValueForLessThan, ValueForEqual);
|
||||
|
||||
public static object convert(object value, object comparand, IComparer comparer, object value_for_less_than, object value_for_greater_than, object value_for_equal_to) {
|
||||
int result = compare(value, comparand, comparer);
|
||||
if (result < 0)
|
||||
return value_for_less_than;
|
||||
if (result > 0)
|
||||
return value_for_greater_than;
|
||||
return value_for_equal_to;
|
||||
}
|
||||
|
||||
public static int compare(object value, object comparand, IComparer comparer) {
|
||||
if (comparer != null) {
|
||||
return comparer.Compare(value, comparand);
|
||||
} else if (value is IComparable comparable) {
|
||||
return comparable.CompareTo(comparand);
|
||||
} else {
|
||||
return System.Collections.Comparer.Default.Compare(value, comparand);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class IsLessThanConverter : ComparisonConverter {
|
||||
public IsLessThanConverter() {
|
||||
ValueForGreaterThan = false;
|
||||
ValueForEqual = false;
|
||||
ValueForLessThan = true;
|
||||
}
|
||||
}
|
||||
|
||||
class IsGreaterThanConverter : ComparisonConverter {
|
||||
public IsGreaterThanConverter() {
|
||||
ValueForLessThan = false;
|
||||
ValueForEqual = false;
|
||||
ValueForGreaterThan = true;
|
||||
}
|
||||
}
|
||||
|
||||
class DynamicComparisonConverter : IMultiValueConverter {
|
||||
public object ValueForGreaterThan { get; set; } = 1;
|
||||
public object ValueForLessThan { get; set; } = -1;
|
||||
public object ValueForEqualTo { get; set; }
|
||||
public IComparer Comparer { get; set; }
|
||||
|
||||
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture) {
|
||||
ExceptionAssert.array_length(values, 2, nameof(values));
|
||||
if (values.Contains(DependencyProperty.UnsetValue))
|
||||
return new ValidationResult(false, null);
|
||||
return ComparisonConverter.convert(values[0], values[1], Comparer, ValueForLessThan, ValueForGreaterThan, ValueForEqualTo);
|
||||
}
|
||||
|
||||
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture) =>
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
|
||||
class DynamicIsLessThanConverter : DynamicComparisonConverter {
|
||||
public DynamicIsLessThanConverter() {
|
||||
ValueForLessThan = true;
|
||||
ValueForEqualTo = true;
|
||||
ValueForGreaterThan = false;
|
||||
}
|
||||
}
|
||||
|
||||
class DynamicIsGreaterThanConverter : DynamicComparisonConverter {
|
||||
public DynamicIsGreaterThanConverter() {
|
||||
ValueForLessThan = false;
|
||||
ValueForEqualTo = false;
|
||||
ValueForGreaterThan = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
using bts.utils;
|
||||
using mbts.ui.utility.text;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Text;
|
||||
using System.Windows;
|
||||
using System.Windows.Data;
|
||||
|
||||
namespace mbts.ui.converters {
|
||||
class FontWeightDisplayTextConverter : IValueConverter {
|
||||
// TODO (when there reallllly isn't anything else to do):
|
||||
// come up with interpolation naming system (e.g., if weight is between 'Light' and 'Super Light,' name it 'Semi Light');
|
||||
// below is the beginning of such an effort.
|
||||
|
||||
//static SortedList<FontWeight, string> _font_weight_cache =
|
||||
// new SortedList<FontWeight, string>(Comparer<int>.Default.wrap<int, FontWeight>(fw => fw.ToOpenTypeWeight()));
|
||||
|
||||
//static FontWeightDisplayTextConverter() {
|
||||
// foreach (var fw in TextUtils.SystemFontWeights)
|
||||
// _font_weight_cache.Add(fw, fw.ToString());
|
||||
//}
|
||||
|
||||
public object Convert(object value, Type targetType, object parameter, CultureInfo culture) {
|
||||
if (value is FontWeight fw) {
|
||||
string rv = fw.ToString();
|
||||
if (int.TryParse(rv, out var open_type_weight)) {
|
||||
// msoft doesn't have a pretty name
|
||||
rv = _fallback_name(open_type_weight);
|
||||
}
|
||||
return rv;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) =>
|
||||
throw new NotImplementedException();
|
||||
|
||||
static string _fallback_name(int open_type_weight) {
|
||||
switch (open_type_weight) {
|
||||
case 350: return "Semi Light";
|
||||
default: return open_type_weight.ToString();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5,11 +5,11 @@ using System.Windows.Controls;
|
||||
using System.Windows.Data;
|
||||
using System.Windows.Media;
|
||||
|
||||
namespace mbts.ui.converters
|
||||
{
|
||||
namespace mbts.ui.converters {
|
||||
[ValueConversion(typeof(ImageSource), typeof(Image))]
|
||||
internal class ImageSourceIconConverter : IValueConverter
|
||||
{
|
||||
class ImageSourceIconConverter : IValueConverter {
|
||||
public static ImageSourceIconConverter Instance => new ImageSourceIconConverter();
|
||||
|
||||
public Thickness Margin { get; set; } = new Thickness(0);
|
||||
public double Width { get; set; } = double.NaN;
|
||||
public double Height { get; set; } = double.NaN;
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Data;
|
||||
|
||||
namespace mbts.ui.converters {
|
||||
[ValueConversion(typeof(int), typeof(int))]
|
||||
class IntAdditionConverter : IValueConverter {
|
||||
public int Value { get; set; }
|
||||
|
||||
public object Convert(object value, Type targetType, object parameter, CultureInfo culture) {
|
||||
if (value is int i)
|
||||
return i + Value;
|
||||
return new ValidationResult(false, "Not an integer.");
|
||||
}
|
||||
|
||||
|
||||
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) {
|
||||
if (value is int i)
|
||||
return i - Value;
|
||||
return new ValidationResult(false, "Not an integer.");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
using System.Globalization;
|
||||
using System.Windows.Data;
|
||||
|
||||
namespace mbts.ui.converters {
|
||||
class TypeConverterWrapper : IValueConverter {
|
||||
public TypeConverter TypeConverter { get; set; }
|
||||
|
||||
public object Convert(object value, Type targetType, object parameter, CultureInfo culture) =>
|
||||
TypeConverter.ConvertFrom(null, culture, value);
|
||||
|
||||
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) =>
|
||||
TypeConverter.ConvertTo(null, culture, value, targetType);
|
||||
}
|
||||
}
|
||||
@@ -13,12 +13,21 @@ namespace mbts.ui.converters {
|
||||
|
||||
public IList Map => _map;
|
||||
public object Default { get; set; }
|
||||
public IValueConverter DefaultConverter { get; set; }
|
||||
|
||||
public object Convert(object value, Type targetType, object parameter, CultureInfo culture) =>
|
||||
_map.TryGetValue(value ?? KeyValue.NullPlaceholder, out var kv) ? kv.Value : Default;
|
||||
_map.TryGetValue(value ?? KeyValue.NullPlaceholder, out var kv) ?
|
||||
kv.Value :
|
||||
DefaultConverter == null ?
|
||||
Default :
|
||||
DefaultConverter.Convert(value, targetType, parameter, culture);
|
||||
|
||||
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) =>
|
||||
_map.TryGetFirst(kv => Equals(kv.Value, value), out KeyValue result) ? result.Key : Default;
|
||||
_map.TryGetFirst(kv => Equals(kv.Value, value), out KeyValue result) ?
|
||||
result.Key :
|
||||
DefaultConverter == null ?
|
||||
Default :
|
||||
DefaultConverter.ConvertBack(value, targetType, parameter, culture);
|
||||
}
|
||||
|
||||
class KeyValue {
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using System.Windows;
|
||||
using System.Windows.Data;
|
||||
|
||||
namespace mbts.ui.converters {
|
||||
[ValueConversion(typeof(SizeToContent), typeof(double))]
|
||||
abstract class WindowSizeThresholdConverter : IValueConverter {
|
||||
readonly SizeToContent _size_to_content_flag;
|
||||
|
||||
protected WindowSizeThresholdConverter(SizeToContent size_to_content_flag) =>
|
||||
_size_to_content_flag = size_to_content_flag;
|
||||
|
||||
public double SizeToContentThreshold { get; set; } = double.NaN;
|
||||
public double Threshold { get; set; }
|
||||
|
||||
public object Convert(object value, Type targetType, object parameter, CultureInfo culture) {
|
||||
if (value is SizeToContent stc) {
|
||||
if ((stc & _size_to_content_flag) == _size_to_content_flag) {
|
||||
if (double.IsNaN(SizeToContentThreshold)) {
|
||||
if (parameter is double d)
|
||||
return d;
|
||||
} else {
|
||||
return SizeToContentThreshold;
|
||||
}
|
||||
}
|
||||
}
|
||||
return Threshold;
|
||||
}
|
||||
|
||||
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) =>
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
|
||||
[ValueConversion(typeof(SizeToContent), typeof(double))]
|
||||
sealed class WindowMinWidthConverter : WindowSizeThresholdConverter {
|
||||
public WindowMinWidthConverter() : base(SizeToContent.Width) { }
|
||||
}
|
||||
|
||||
[ValueConversion(typeof(SizeToContent), typeof(double))]
|
||||
sealed class WindowMaxWidthConverter : WindowSizeThresholdConverter {
|
||||
public WindowMaxWidthConverter() : base(SizeToContent.Width) =>
|
||||
Threshold = double.PositiveInfinity;
|
||||
}
|
||||
|
||||
[ValueConversion(typeof(SizeToContent), typeof(double))]
|
||||
sealed class WindowMinHeightConverter : WindowSizeThresholdConverter {
|
||||
public WindowMinHeightConverter() : base(SizeToContent.Height) { }
|
||||
}
|
||||
|
||||
[ValueConversion(typeof(SizeToContent), typeof(double))]
|
||||
sealed class WindowMaxHeightConverter : WindowSizeThresholdConverter {
|
||||
public WindowMaxHeightConverter() : base(SizeToContent.Height) =>
|
||||
Threshold = double.PositiveInfinity;
|
||||
}
|
||||
}
|
||||
@@ -156,6 +156,62 @@ namespace mbts.ui {
|
||||
}
|
||||
}
|
||||
|
||||
public static IDisposable connect_vertical_insertion_adorner(ItemsControl container, FrameworkElement root = null) {
|
||||
VerticalInsertionAdorner insertion_adorner = null;
|
||||
UIElement target = null;
|
||||
VerticalInsertionAdorner.TargetSide target_side = VerticalInsertionAdorner.TargetSide.Top;
|
||||
if (root == null)
|
||||
root = container;
|
||||
return Disposable.Create(
|
||||
root.SubscribePreviewDragEnter(on_drag_enter),
|
||||
root.SubscribePreviewDragLeave((s, e) => remove_adorner()),
|
||||
root.SubscribePreviewDragOver(on_drag_over),
|
||||
root.SubscribePreviewDrop((s, e) => remove_adorner()),
|
||||
Disposable.Create(remove_adorner));
|
||||
void on_drag_over(object sender, DragEventArgs e) {
|
||||
_determine_drop_target(e);
|
||||
_update_insertion_adorner();
|
||||
e.Effects = DragDropEffects.Move;
|
||||
e.Handled = true;
|
||||
}
|
||||
void on_drag_enter(object sender, DragEventArgs e) {
|
||||
_determine_drop_target(e);
|
||||
_update_insertion_adorner();
|
||||
e.Effects = e.AllowedEffects;
|
||||
}
|
||||
// Draw an indicator where the drop will insert the item
|
||||
void _update_insertion_adorner() {
|
||||
if (insertion_adorner == null) {
|
||||
insertion_adorner = new VerticalInsertionAdorner(target, target_side);
|
||||
} else insertion_adorner.Side = target_side;
|
||||
}
|
||||
void _determine_drop_target(DragEventArgs e) {
|
||||
var orig_target = target;
|
||||
var orig_side = target_side;
|
||||
target = null;
|
||||
var pt = e.GetPosition(container);
|
||||
pt.X = MathUtils.within_range(0, container.ActualWidth - 2, pt.X);
|
||||
pt.Y = MathUtils.within_range(0, container.ActualHeight - 2, pt.Y);
|
||||
if (container.InputHitTest(pt) is DependencyObject hit) {
|
||||
target = container.ContainerFromElement(hit) as UIElement;
|
||||
if (target != null) {
|
||||
Point target_position = e.GetPosition(target);
|
||||
target_side = target_position.Y < (target.RenderSize.Height / 2) ?
|
||||
VerticalInsertionAdorner.TargetSide.Top :
|
||||
VerticalInsertionAdorner.TargetSide.Bottom;
|
||||
if (target != orig_target || target_side != orig_side)
|
||||
remove_adorner();
|
||||
}
|
||||
}
|
||||
}
|
||||
void remove_adorner() {
|
||||
if (insertion_adorner != null) {
|
||||
insertion_adorner.Detach();
|
||||
insertion_adorner = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[Conditional("DEBUG")]
|
||||
private static void log(string message, params object[] args) {
|
||||
Console.WriteLine(message, args);
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
using mbts.utils;
|
||||
using bts;
|
||||
using mbts.utils;
|
||||
using System;
|
||||
using System.Reactive.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Controls.Primitives;
|
||||
using System.Windows.Data;
|
||||
using CompositeDisposable = System.Reactive.Disposables.CompositeDisposable;
|
||||
|
||||
namespace mbts.ui.extensions {
|
||||
@@ -81,6 +83,33 @@ namespace mbts.ui.extensions {
|
||||
}
|
||||
}
|
||||
|
||||
public static IObservable<bool> ObserveIsSelected(this TabItem tab_item) {
|
||||
int subscription_count = 0;
|
||||
BindingProxy proxy = null;
|
||||
return Observable.Create<bool>(o => {
|
||||
if (subscription_count++ == 0) {
|
||||
Log.assert(proxy == null, "Potential memory leak detected: binding proxy should be null when there are 0 subscriptions.");
|
||||
proxy = new BindingProxy();
|
||||
proxy.SetBinding(BindingProxy.ValueProperty, new Binding(nameof(TabItem.IsSelected)) { Source = tab_item });
|
||||
}
|
||||
bool last_observed = tab_item.IsSelected;
|
||||
o.OnNext(last_observed);
|
||||
proxy.ValueChanged += on_value_changed;
|
||||
return Disposable.CreateDisposeOnce(dispose);
|
||||
void dispose() {
|
||||
proxy.ValueChanged -= on_value_changed;
|
||||
if (--subscription_count == 0) {
|
||||
Log.assert(proxy != null, "Binding proxy should not be null when subscriptions are active.");
|
||||
proxy = null;
|
||||
}
|
||||
}
|
||||
void on_value_changed(object d, DependencyPropertyChangedEventArgs e) {
|
||||
if ((e.NewValue is bool b && b) != last_observed)
|
||||
o.OnNext(last_observed = !last_observed);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public static IObservable<bool> ObserveIsChecked(this ToggleButton toggle_button) =>
|
||||
Observable.Create<bool>(o => {
|
||||
toggle_button.Checked += on_checked;
|
||||
|
||||
@@ -1,10 +1,14 @@
|
||||
using mbts.config;
|
||||
using mbts.ui.controls;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Drawing;
|
||||
using System.Drawing.Drawing2D;
|
||||
using System.Windows;
|
||||
using System.Windows.Forms;
|
||||
using System.Windows.Media;
|
||||
using Point = System.Windows.Point;
|
||||
|
||||
namespace mbts.ui.dpi {
|
||||
public static class HighDpiExtensions {
|
||||
@@ -119,5 +123,46 @@ namespace mbts.ui.dpi {
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// Calculates the value to be used for layout rounding at high DPI.
|
||||
/// </summary>
|
||||
/// <param name="value">Input value to be rounded.</param>
|
||||
/// <param name="dpiScale">Ratio of screen's DPI to layout DPI</param>
|
||||
/// <returns>Adjusted value that will produce layout rounding on screen at high dpi.</returns>
|
||||
/// <remarks>This is a layout helper method. It takes DPI into account and also does not return
|
||||
/// the rounded value if it is unacceptable for layout, e.g. Infinity or NaN. It's a helper associated with
|
||||
/// UseLayoutRounding property and should not be used as a general rounding utility.</remarks>
|
||||
public static double RoundLayoutValue(this double value) =>
|
||||
RoundLayoutValue(value, dpi_ratio);
|
||||
|
||||
public static double RoundLayoutValue(double value, double dpiScale) {
|
||||
if (DoubleUtil.AreClose(dpiScale, 1.0))
|
||||
return Math.Round(value);
|
||||
double result = Math.Round(value * dpiScale) / dpiScale;
|
||||
return DoubleHelper.IsNaNOrInfinity(result) || DoubleUtil.AreClose(result, double.MaxValue) ? value : result;
|
||||
}
|
||||
|
||||
public static Point RoundToLayout(this Point point) =>
|
||||
new Point(RoundLayoutValue(point.X), RoundLayoutValue(point.Y));
|
||||
|
||||
public static Thickness RoundToLayout(this Thickness thickness) =>
|
||||
new Thickness(
|
||||
thickness.Left.RoundLayoutValue(),
|
||||
thickness.Top.RoundLayoutValue(),
|
||||
thickness.Right.RoundLayoutValue(),
|
||||
thickness.Bottom.RoundLayoutValue());
|
||||
|
||||
public static GuidelineSet AntiAliasGuidelines(this Rect rect) =>
|
||||
rect.AntiAliasGuidelines(dpi_ratio);
|
||||
|
||||
public static GuidelineSet AntiAliasGuidelines(this Rect rect, double pen_thickness) {
|
||||
var half_pen_thickness = pen_thickness / 2;
|
||||
//half_pen_thickness = 0;
|
||||
var guidelines = new GuidelineSet(
|
||||
new[] { rect.Left.RoundLayoutValue() - half_pen_thickness, rect.Right.RoundLayoutValue() + half_pen_thickness },
|
||||
new[] { rect.Top.RoundLayoutValue() - half_pen_thickness, rect.Bottom.RoundLayoutValue() + half_pen_thickness });
|
||||
return guidelines;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,17 +2,12 @@
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows.Markup;
|
||||
using System.Windows.Media;
|
||||
|
||||
namespace mbts.ui
|
||||
{
|
||||
namespace mbts.ui {
|
||||
[MarkupExtensionReturnType(typeof(Brush))]
|
||||
public class BrushExtension : MarkupExtension
|
||||
{
|
||||
class BrushExtension : MarkupExtension {
|
||||
public BrushExtension() : this(Colors.Black) { }
|
||||
public BrushExtension(Color Color) : this(Color, 1.0) { }
|
||||
public BrushExtension(Color Color, double Opacity) { this.Color = Color; this.Opacity = Opacity; }
|
||||
@@ -27,6 +22,70 @@ namespace mbts.ui
|
||||
}
|
||||
}
|
||||
|
||||
[MarkupExtensionReturnType(typeof(Brush))]
|
||||
class FrozenBrush : MarkupExtension {
|
||||
[ThreadStatic]
|
||||
static Dictionary<BrushKey, SolidColorBrush> _brushes;
|
||||
|
||||
double _opacity = 1;
|
||||
|
||||
public FrozenBrush(Color color) =>
|
||||
Color = color;
|
||||
|
||||
public Color Color { get; set; }
|
||||
public double Opacity {
|
||||
get => _opacity;
|
||||
set => _opacity = value < 0 || value > 1 ?
|
||||
throw new ArgumentException("Must be between 0 and 1 (inclusive).", nameof(value)) :
|
||||
value;
|
||||
}
|
||||
|
||||
public static SolidColorBrush get_brush(Color color, double opacity = 1.0) {
|
||||
if (_brushes == null)
|
||||
_brushes = new Dictionary<BrushKey, SolidColorBrush>();
|
||||
unchecked {
|
||||
ushort opac = opacity == 1 ? ushort.MaxValue : (ushort)(opacity * 100_000);
|
||||
// color has a perfect hash (it is 32 bits, so it damn well better).
|
||||
var key = new BrushKey(color.GetHashCode(), opac);
|
||||
if (!_brushes.TryGetValue(key, out var brush)) {
|
||||
_brushes[key] = brush = new SolidColorBrush(color).frozen();
|
||||
}
|
||||
return brush;
|
||||
}
|
||||
}
|
||||
|
||||
public override object ProvideValue(IServiceProvider serviceProvider) =>
|
||||
get_brush(Color, Opacity);
|
||||
|
||||
readonly struct BrushKey : IEquatable<BrushKey> {
|
||||
readonly int _color;
|
||||
// bc opacity does not need more precision than 16 bits; 100,000 discrete values is more than enough
|
||||
// (65,536 should avoid has collisions stemming from opacity; also leaves 16 bits to encode color).
|
||||
readonly ushort _opacity;
|
||||
|
||||
public BrushKey(int color, ushort opacity) {
|
||||
_color = color;
|
||||
_opacity = opacity;
|
||||
}
|
||||
|
||||
public bool Equals(BrushKey other) =>
|
||||
_color == other._color &&
|
||||
_opacity == other._opacity;
|
||||
|
||||
public override bool Equals(object obj) =>
|
||||
obj is BrushKey other && Equals(other);
|
||||
|
||||
public override int GetHashCode() =>
|
||||
// ushort.MaxValue indicates opacity of '1.'
|
||||
// The value we have is the actual opacity multiplied by 100,000;
|
||||
// so 1 would actually be a very small opacity: 0.0001.
|
||||
_opacity == ushort.MaxValue ?
|
||||
// the last bit indicates if opacity is zero
|
||||
_color | -1 :
|
||||
((ushort)_color | (_opacity << 16) | -1) ^ -1;
|
||||
}
|
||||
}
|
||||
|
||||
public class ColorTypeConverter : TypeConverter
|
||||
{
|
||||
public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType) { return sourceType == typeof(string); }
|
||||
|
||||
Loaded 100 of 140 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user