Compare commits
53
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d421fdb41f | ||
|
|
6cbff72bc3 | ||
|
|
d81f5b87fc | ||
|
|
fe6f4ffa92 | ||
|
|
7841faafa2 | ||
|
|
89df89db11 | ||
|
|
03867914cb | ||
|
|
52f62c19a4 | ||
|
|
860bda388f | ||
|
|
0b73e2744e | ||
|
|
e5d23a987e | ||
|
|
6ac3eeca2e | ||
|
|
3b73ba25d2 | ||
|
|
34eed73af5 | ||
|
|
2f1f1c007c | ||
|
|
cac4f5da37 | ||
|
|
58dab25b89 | ||
|
|
7d100da9b1 | ||
|
|
c389a47a4a | ||
|
|
d3d069cf17 | ||
|
|
9576122aeb | ||
|
|
a32b40bef6 | ||
|
|
d9f459ccb5 | ||
|
|
18bdeea765 | ||
|
|
87bf9a37cc | ||
|
|
96b6c0fc1d | ||
|
|
0cee53e287 | ||
|
|
db135a36d9 | ||
|
|
bd8dbe0071 | ||
|
|
4f0a01175e | ||
|
|
1f8af68b60 | ||
|
|
5f2a2a7a56 | ||
|
|
6a0e00222c | ||
|
|
c087999c4b | ||
|
|
763f8f006d | ||
|
|
32257f257e | ||
|
|
4903545642 | ||
|
|
6eb3cf5cf6 | ||
|
|
cdcd0d0436 | ||
|
|
e04ac0592b | ||
|
|
0a3cbc8539 | ||
|
|
fb2385041c | ||
|
|
98685e59b3 | ||
|
|
bf9e6a645a | ||
|
|
6d02d9a508 | ||
|
|
2a8a459cdd | ||
|
|
e25d039f3b | ||
|
|
5cdaa60b86 | ||
|
|
ff33d0d735 | ||
|
|
8ba1c7a602 | ||
|
|
8ef5dd56ee | ||
|
|
c3c1a33081 | ||
|
|
4c0461311f |
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),
|
||||
@@ -125,6 +126,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);
|
||||
|
||||
@@ -13,6 +13,47 @@
|
||||
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>
|
||||
|
||||
<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);
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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,150 @@
|
||||
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,
|
||||
OrderRecordTuplePredicate.PredicateTypeOneofCase.SentEvent
|
||||
};
|
||||
|
||||
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;
|
||||
case OrderRecordTuplePredicate.PredicateTypeOneofCase.SentEvent:
|
||||
pred.sent_event = new OrderSentEventPredicate {
|
||||
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,150 @@
|
||||
<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>
|
||||
<Grid.Margin>
|
||||
<Thickness Left="{StaticResource HorizontalPush2}" Right="{StaticResource HorizontalPush2}"/>
|
||||
</Grid.Margin>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="*"/>
|
||||
</Grid.RowDefinitions>
|
||||
<c:ActionMenu
|
||||
HorizontalAlignment="Right"
|
||||
Margin="0,2,0,0"
|
||||
ItemsSource="{Binding DataContext.action_menu_items, RelativeSource={RelativeSource AncestorType=usp:ShellSettingsDialog}}"
|
||||
Style="{StaticResource SparkToolBarItem}"
|
||||
/>
|
||||
<TabControl x:Name="Tabs" HorizontalAlignment="Stretch" MinHeight="480" MinWidth="500" Margin="0,2,0,0" Padding="6">
|
||||
<TabControl.ItemContainerStyle>
|
||||
<Style TargetType="TabItem">
|
||||
<Setter Property="MinWidth" Value="60"/>
|
||||
<Setter Property="HorizontalAlignment" Value="Center"/>
|
||||
</Style>
|
||||
</TabControl.ItemContainerStyle>
|
||||
<TabItem Header="Grid">
|
||||
<ItemsControl x:Name="ParameterItemsControl" Grid.IsSharedSizeScope="True" Margin="8,16,0,0">
|
||||
<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,133 +1,342 @@
|
||||
using bts.collections;
|
||||
using mbts.spark;
|
||||
using mbts.ui.controls;
|
||||
using mbts.ui.settings;
|
||||
using mbts.ui.shell;
|
||||
using mbts.utils;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.ComponentModel;
|
||||
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;
|
||||
|
||||
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);
|
||||
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;
|
||||
}
|
||||
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.utilities;
|
||||
using mbts.ui.utility;
|
||||
using mbts.ui.viewmodels;
|
||||
using mbts.utils;
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
using System.Reactive.Linq;
|
||||
using System.Windows;
|
||||
using System.Windows.Media;
|
||||
|
||||
namespace bts.shell.pnl {
|
||||
class PNLColumnSettingsModeVM : GridColumnSettingsVM {
|
||||
readonly ObservableAggregateCollection<object> _aggregate_parameters = new ObservableAggregateCollection<object>();
|
||||
IDisposable _param_sync;
|
||||
List<(string rule, PNLCellModeVM vm)> _rules = new List<(string rule, PNLCellModeVM vm)>();
|
||||
|
||||
public PNLColumnSettingsModeVM(string mode) {
|
||||
this.mode = mode;
|
||||
show_horizontal_alignment = false;
|
||||
show_width = false;
|
||||
can_edit_header = false;
|
||||
header.can_override = false;
|
||||
parameter_group = new ParameterGroupVM(mode);
|
||||
_param_sync = ObservableAutoWrapper.connect(parameters, parameter_group.parameters);
|
||||
}
|
||||
|
||||
public ParameterGroupVM parameter_group { get; }
|
||||
public string mode { get; }
|
||||
public IEnumerable<(string rule, PNLCellModeVM vm)> rules => _rules;
|
||||
|
||||
public GridColumnSettings apply_to(GridColumnSettings target) {
|
||||
return target.with(
|
||||
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_rules(IReadOnlyDictionary<string, PNLAnimation> rules) {
|
||||
Disposable.dispose(ref _param_sync);
|
||||
_rules = rules.Select(
|
||||
kv => {
|
||||
var rule_vm = new PNLCellModeVM($"When {DisplayNameHelper.expression_snake_to_title_case(kv.Key)}", kv.Key);
|
||||
rule_vm.load(kv.Value);
|
||||
return (kv.Key, rule_vm);
|
||||
})
|
||||
.ToList();
|
||||
if (_rules.Count > 0) {
|
||||
var row_defaults = new PNLCellModeVM("Default") { is_expanded = true };
|
||||
parameter_group.parameters.SetItems(_rules.Select(r => r.vm).Prepend(row_defaults));
|
||||
} else {
|
||||
sync_params();
|
||||
_param_sync = ObservableAutoWrapper.connect(parameters, parameter_group.parameters);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class PNLRulesVM : ParameterGroupVM {
|
||||
PNLCellModeRuleSetVM _selected_cell_mode;
|
||||
|
||||
public PNLRulesVM(string display_name) : base(display_name) {
|
||||
}
|
||||
|
||||
public PNLCellModeRuleSetVM selected_cell_mode { get => _selected_cell_mode; set => Set(ref _selected_cell_mode, value); }
|
||||
|
||||
public void load_from(GridColumnSettings value, IReadOnlyDictionary<string, PNLAnimation> triggers, GridColumnSettings group, IReadOnlyDictionary<string, PNLAnimation> group_triggers, IReadOnlyDictionary<string, PNLAnimation> header_triggers, GridColumnSettings header) {
|
||||
parameters.Clear();
|
||||
var default_mode = new PNLCellModeRuleSetVM("Default");
|
||||
default_mode.default_vm.load(value);
|
||||
default_mode.group_vm.load(group);
|
||||
default_mode.header_vm.load(header);
|
||||
parameters.Add(default_mode);
|
||||
foreach (var kv in triggers) {
|
||||
var rule_vm = new PNLCellModeRuleSetVM($"When {DisplayNameHelper.expression_snake_to_title_case(kv.Key)}", 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);
|
||||
parameters.Add(rule_vm);
|
||||
}
|
||||
if (selected_cell_mode == null)
|
||||
selected_cell_mode = default_mode;
|
||||
}
|
||||
}
|
||||
|
||||
class PNLCellModeRuleSetVM : ParameterGroupVM {
|
||||
PNLCellRuleModeVM _selected_cell_mode;
|
||||
|
||||
public PNLCellModeRuleSetVM(string display_name, string rule = null) : base(display_name) {
|
||||
parameters.AddRange(default_vm, group_vm, header_vm);
|
||||
_selected_cell_mode = default_vm;
|
||||
}
|
||||
|
||||
public PNLCellRuleModeVM selected_cell_mode { get => _selected_cell_mode; set => Set(ref _selected_cell_mode, value); }
|
||||
public PNLCellRuleModeVM default_vm { get; } = new PNLCellRuleModeVM("Row") { is_expanded = true };
|
||||
public PNLCellRuleModeVM group_vm { get; } = new PNLCellRuleModeVM("Group Row");
|
||||
public PNLCellRuleModeVM header_vm { get; } = new PNLCellRuleModeVM("Header Row");
|
||||
}
|
||||
|
||||
class PNLCellRuleModeVM : ParameterGroupVM {
|
||||
public PNLCellRuleModeVM(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;
|
||||
}
|
||||
|
||||
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(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 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(GridColumnSettings value) {
|
||||
// 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
|
||||
font_color.is_overridden = value.font_color_override.HasValue;
|
||||
if (font_color.is_overridden)
|
||||
font_color.override_value = value.font_color_override.Value;
|
||||
// background
|
||||
background.is_overridden = value.background_color_override.HasValue;
|
||||
if (background.is_overridden)
|
||||
background.override_value = value.background_color_override.Value;
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
class PNLCellModeVM : ParameterGroupVM {
|
||||
public PNLCellModeVM(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;
|
||||
}
|
||||
|
||||
public string rule { get; }
|
||||
|
||||
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(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 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(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();
|
||||
}
|
||||
}
|
||||
|
||||
class PNLColumnSettingsVM : GridColumnSettingsVM {
|
||||
readonly ObservableCollection<PNLColumnSettingsModeVM> _modes = new ObservableCollection<PNLColumnSettingsModeVM>();
|
||||
readonly ParameterGroupVM _column_params_group = new ParameterGroupVM("Column Layout");
|
||||
readonly ObservableAggregateCollection<object> _cell_appearance_groups = new ObservableAggregateCollection<object>();
|
||||
|
||||
public PNLColumnSettingsVM() : base(null) {
|
||||
_column_params_group.parameters.AddRange(header, width, horizontal_alignment);
|
||||
// hide header/width/align params in group/header vms
|
||||
group_column_vm.can_edit_width = false;
|
||||
group_column_vm.can_edit_header = false;
|
||||
group_column_vm.show_horizontal_alignment = false;
|
||||
group_column_vm.header.can_override = false;
|
||||
group_column_vm.sync_params();
|
||||
// header
|
||||
header_column_vm.show_width = false;
|
||||
header_column_vm.show_horizontal_alignment = false;
|
||||
header_column_vm.header.can_override = false;
|
||||
header_column_vm.sync_params();
|
||||
// header
|
||||
default_vm.show_width = false;
|
||||
default_vm.show_horizontal_alignment = false;
|
||||
default_vm.header.can_override = false;
|
||||
default_vm.sync_params();
|
||||
|
||||
default_vm.parameter_group.is_expanded = true;
|
||||
fonts_and_colors.parameters.Add(default_vm.parameter_group);
|
||||
fonts_and_colors.parameters.Add(group_column_vm.parameter_group);
|
||||
fonts_and_colors.parameters.Add(header_column_vm.parameter_group);
|
||||
|
||||
apply_flags(GridColumnSettingsFeatureFlags.None);
|
||||
sync_params();
|
||||
}
|
||||
|
||||
public ParameterGroupVM column_layout => _column_params_group;
|
||||
public ParameterGroupVM fonts_and_colors { get; } = new ParameterGroupVM("Fonts/Colors");
|
||||
public PNLColumnSettingsModeVM group_column_vm { get; } = new PNLColumnSettingsModeVM("Group Row");
|
||||
public PNLColumnSettingsModeVM header_column_vm { get; } = new PNLColumnSettingsModeVM("Header Row");
|
||||
public PNLColumnSettingsModeVM default_vm { get; } = new PNLColumnSettingsModeVM("Row");
|
||||
|
||||
protected override IEnumerable<object> _visible_parameters {
|
||||
get {
|
||||
yield return header;
|
||||
yield return width;
|
||||
yield return horizontal_alignment;
|
||||
//yield return default_vm.parameter_group;
|
||||
//yield return group_column_vm.parameter_group;
|
||||
//yield return header_column_vm.parameter_group;
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
if (group != null)
|
||||
group_column_vm.load_from(group);
|
||||
if (header != null)
|
||||
header_column_vm.load_from(header);
|
||||
default_vm.load_rules(triggers);
|
||||
group_column_vm.load_rules(triggers);
|
||||
header_column_vm.load_rules(triggers);
|
||||
if (triggers == null || triggers.Count == 0) {
|
||||
fonts_and_colors.parameters.SetItems(new[] { default_vm.parameter_group, group_column_vm.parameter_group, header_column_vm.parameter_group });
|
||||
} else {
|
||||
fonts_and_colors.parameters.Clear();
|
||||
//var default_mode = new PNLCellModeRuleSetVM("Default");
|
||||
//default_mode.default_vm.load(value);
|
||||
//default_mode.group_vm.load(group);
|
||||
//default_mode.header_vm.load(header);
|
||||
//fonts_and_colors.parameters.Add(default_mode);
|
||||
//foreach (var kv in triggers) {
|
||||
// var rule_vm = new PNLCellModeRuleSetVM( $"When {DisplayNameHelper.expression_snake_to_title_case(kv.Key)}", 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);
|
||||
// fonts_and_colors.parameters.Add(rule_vm);
|
||||
//}
|
||||
|
||||
var rule_set = new PNLRulesVM("Rules Poop");
|
||||
rule_set.load_from(value, triggers, group, group_triggers, header_triggers, header);
|
||||
fonts_and_colors.parameters.Add(rule_set);
|
||||
}
|
||||
}
|
||||
|
||||
public IEnumerable<(string expression, PNLAnimation row, PNLAnimation group, PNLAnimation header)> get_triggers() {
|
||||
var triggers = new Dictionary<string, (PNLAnimation row, PNLAnimation group, PNLAnimation header)>();
|
||||
if (Enum.TryParse(id, out PNLView.Columns col_key)) {
|
||||
foreach (var (rule, vm) in default_vm.rules)
|
||||
triggers[rule] = (vm.create(col_key), null, null);
|
||||
foreach (var (rule, vm) in group_column_vm.rules)
|
||||
triggers.AddOrUpdate(rule, r => (null, vm.create(col_key), null), (r, tup) => (tup.row, vm.create(col_key), tup.header));
|
||||
foreach (var (rule, vm) in header_column_vm.rules)
|
||||
triggers.AddOrUpdate(rule, r => (null, null, vm.create(col_key)), (r, tup) => (tup.row, tup.group, vm.create(col_key)));
|
||||
}
|
||||
return triggers.Select(kv => (kv.Key, kv.Value.row, kv.Value.group, kv.Value.header));
|
||||
}
|
||||
|
||||
public override string ToString() => "Default";
|
||||
|
||||
static IEnumerable<object> _standard_params() {
|
||||
var header = new StringOverrideParameterVM("Header");
|
||||
var width = new DoubleParameterVM("Width");
|
||||
var horizontal_alignment = new HorizontalAlignmentParameterVM("Horizontal Alignment");
|
||||
yield return header;
|
||||
yield return width;
|
||||
yield return horizontal_alignment;
|
||||
}
|
||||
|
||||
static IEnumerable<object> _cell_appearance_params() {
|
||||
var background = new ColorOverrideParameterVM("Background");
|
||||
var font_color = new ColorOverrideParameterVM("Font Color");
|
||||
var font_weight = new FontWeightOverrideParameterVM();
|
||||
yield return background;
|
||||
yield return font_color;
|
||||
yield return font_weight;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,68 +1,76 @@
|
||||
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 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.Linq;
|
||||
using ColumnKeys = bts.shell.pnl.PNLView.Columns;
|
||||
|
||||
namespace bts.shell.pnl {
|
||||
enum CellTypes {
|
||||
Row,
|
||||
Group,
|
||||
Header
|
||||
}
|
||||
|
||||
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 GridSettingsVM();
|
||||
columns = core_settings_vm.columns.CastObservable<GridColumnSettingsVM, PNLColumnSettingsVM>();
|
||||
//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;
|
||||
// });
|
||||
_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 GridSettingsVM 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;
|
||||
public IReadOnlyObservableCollection<PNLColumnSettingsVM> columns { get; }
|
||||
string ITemplateSelectorKey.key => GridSettingsTemplateKeys.PNL;
|
||||
|
||||
public ObservableCollection<NamedVM<GridColumnSettingsVM>> cell_types { get; }
|
||||
|
||||
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++) {
|
||||
@@ -75,146 +83,147 @@ namespace bts.shell.pnl {
|
||||
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_items.Select(vm => vm.key),
|
||||
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.group_column_vm.apply_to(col_settings);
|
||||
return col_settings;
|
||||
}),
|
||||
header_columns: columns.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(value.grid, false);
|
||||
using (core_settings_vm.begin_change_columns()) {
|
||||
core_settings_vm.columns.SetItems(value.grid.columns.Select(c => {
|
||||
var vm = new PNLColumnSettingsVM();
|
||||
var key = EnumHelper.parse<ColumnKeys>(c.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);
|
||||
vm.load_from(col_settings, trigger_animations, group_data?.settings, group_data?.triggers, header_data?.triggers, header_data?.settings);
|
||||
return vm;
|
||||
}));
|
||||
}
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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,359 @@
|
||||
<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:shell="clr-namespace:bts.shell"
|
||||
xmlns:vm="clr-namespace:mbts.ui.viewmodels"
|
||||
xmlns:sp="clr-namespace:mbts.spark"
|
||||
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:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
|
||||
xmlns:sys="clr-namespace:System;assembly=mscorlib"
|
||||
Content="{Binding}"
|
||||
>
|
||||
<UserControl.Resources>
|
||||
<ResourceDictionary>
|
||||
<ResourceDictionary.MergedDictionaries>
|
||||
<usp:GridSettingsResources/>
|
||||
<ResourceDictionary Source="/view/Controls/TabControls/HorizontalTabControl.xaml"/>
|
||||
</ResourceDictionary.MergedDictionaries>
|
||||
<sys:Double x:Key="CellTypesTabWidth">183</sys:Double>
|
||||
<DataTemplate x:Key="RowCellTypeTemplate" DataType="{x:Type pnl:PNLColumnSettingsVM}">
|
||||
<TextBlock>Row Cell Appearance</TextBlock>
|
||||
</DataTemplate>
|
||||
<DataTemplate x:Key="GroupCellTypeTemplate" DataType="{x:Type pnl:PNLColumnSettingsVM}">
|
||||
<TextBlock>Group Cell Appearance</TextBlock>
|
||||
</DataTemplate>
|
||||
<DataTemplate x:Key="HeaderCellTypeTemplate" DataType="{x:Type pnl:PNLColumnSettingsVM}">
|
||||
<TextBlock>Header Cell Appearance</TextBlock>
|
||||
</DataTemplate>
|
||||
<DataTemplate x:Key="ParametersListTemplate">
|
||||
<ListBox VerticalAlignment="Top" MinWidth="426" ItemsSource="{Binding parameters}" Style="{StaticResource NakedListBox}">
|
||||
<ListBox.ItemContainerStyle>
|
||||
<Style TargetType="ListBoxItem" BasedOn="{StaticResource NakedListBoxItem}">
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="ListBoxItem">
|
||||
<Border Background="Transparent">
|
||||
<ContentPresenter HorizontalAlignment="Stretch" VerticalAlignment="Stretch"/>
|
||||
</Border>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
</ListBox.ItemContainerStyle>
|
||||
</ListBox>
|
||||
</DataTemplate>
|
||||
|
||||
<Style x:Key="ParameterGroupTabItem" TargetType="TabItem" BasedOn="{StaticResource LargeMenuControlBase}">
|
||||
<Setter Property="Padding" Value="14,10,14,10"/>
|
||||
<Setter Property="Background" Value="#FFE8E8E8"/>
|
||||
<Setter Property="Width" Value="{StaticResource CellTypesTabWidth}"/>
|
||||
<Setter Property="HorizontalContentAlignment" Value="Left"/>
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="TabItem">
|
||||
<Border
|
||||
x:Name="Border"
|
||||
CornerRadius="0"
|
||||
BorderThickness="{TemplateBinding BorderThickness}"
|
||||
BorderBrush="{TemplateBinding BorderBrush}"
|
||||
Background="{TemplateBinding Background}"
|
||||
Padding="{TemplateBinding Padding}"
|
||||
Height="{TemplateBinding Height}"
|
||||
Width="{TemplateBinding Width}"
|
||||
SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"
|
||||
>
|
||||
<u:StateManager.Groups>
|
||||
<u:StateGroupsManager AttachToMembers="True">
|
||||
<u:StateDefinitionCollection>
|
||||
<u:MouseStateDefinition MouseSource="{Binding Group.Target.TemplatedParent, RelativeSource={RelativeSource Self}}"/>
|
||||
</u:StateDefinitionCollection>
|
||||
</u:StateGroupsManager>
|
||||
</u:StateManager.Groups>
|
||||
<VisualStateManager.VisualStateGroups>
|
||||
<VisualStateGroup Name="MouseStates">
|
||||
<VisualState Name="Normal"/>
|
||||
<VisualState Name="MouseOver" Storyboard="{StaticResource Button.MouseOver.Storyboard}"/>
|
||||
<VisualState Name="Pressed" Storyboard="{StaticResource Button.Pressed.Storyboard}"/>
|
||||
</VisualStateGroup>
|
||||
</VisualStateManager.VisualStateGroups>
|
||||
<ContentPresenter
|
||||
Name="presenter"
|
||||
Grid.Column="1"
|
||||
ContentSource="Header"
|
||||
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
|
||||
VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
|
||||
RecognizesAccessKey="True"
|
||||
/>
|
||||
</Border>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
<DataTemplate x:Key="ParameterGroupTabHeaderTemplate">
|
||||
<TextBlock x:Name="text" FontSize="13" Text="{Binding name}"/>
|
||||
<DataTemplate.Triggers>
|
||||
<DataTrigger Binding="{Binding IsSelected, RelativeSource={RelativeSource AncestorType=TabItem}}" Value="True">
|
||||
<Setter TargetName="text" Property="TextBlock.FontWeight" Value="Bold"/>
|
||||
</DataTrigger>
|
||||
</DataTemplate.Triggers>
|
||||
</DataTemplate>
|
||||
<Style x:Key="ParameterGroupTabControl" TargetType="TabControl">
|
||||
<Setter Property="Background" Value="Transparent"/>
|
||||
<Setter Property="BorderThickness" Value="0"/>
|
||||
<Setter Property="VerticalAlignment" Value="Stretch"/>
|
||||
<Setter Property="TabStripPlacement" Value="Left"/>
|
||||
<Setter Property="ItemContainerStyle" Value="{StaticResource ParameterGroupTabItem}"/>
|
||||
<Setter Property="ItemTemplate" Value="{StaticResource ParameterGroupTabHeaderTemplate}"/>
|
||||
<Setter Property="ItemsSource" Value="{Binding parameters}"/>
|
||||
<Setter Property="ContentTemplate" Value="{StaticResource ParametersListTemplate}"/>
|
||||
</Style>
|
||||
|
||||
<DataTemplate DataType="{x:Type pnl:PNLRulesVM}">
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="*"/>
|
||||
</Grid.RowDefinitions>
|
||||
<!--<Rectangle Grid.Row="1" HorizontalAlignment="Left" VerticalAlignment="Stretch" Width="{StaticResource CellTypesTabWidth}" Fill="#FFE8E8E8"/>-->
|
||||
<c:HorizontalTabControl
|
||||
Grid.Row="1"
|
||||
Background="Transparent"
|
||||
BorderThickness="0,1,0,0"
|
||||
VerticalAlignment="Stretch"
|
||||
ItemsSource="{Binding parameters}"
|
||||
SelectedItem="{Binding selected_cell_mode}"
|
||||
DisplayMemberPath="name"
|
||||
>
|
||||
<TabControl.ItemContainerStyle>
|
||||
<Style TargetType="TabItem" BasedOn="{StaticResource ParameterGroupTabItem}">
|
||||
<Setter Property="VerticalAlignment" Value="Bottom"/>
|
||||
<Setter Property="Background">
|
||||
<Setter.Value>
|
||||
<SolidColorBrush Color="Transparent"/>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
<Setter Property="Padding" Value="4"/>
|
||||
<Setter Property="Width" Value="{x:Static sys:Double.NaN}"/>
|
||||
<Setter Property="BorderThickness" Value="1"/>
|
||||
<Setter Property="ContentTemplate">
|
||||
<Setter.Value>
|
||||
<DataTemplate>
|
||||
<ItemsControl ItemsSource="{Binding parameters}"/>
|
||||
</DataTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
<Style.Triggers>
|
||||
<Trigger Property="IsSelected" Value="True">
|
||||
<!--<Setter Property="BorderBrush" Value=""/>-->
|
||||
<Setter Property="BorderThickness" Value="1,1,1,0"/>
|
||||
<Setter Property="Padding" Value="4,6,4,8"/>
|
||||
<Setter Property="FontWeight" Value="Bold"/>
|
||||
</Trigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
</TabControl.ItemContainerStyle>
|
||||
</c:HorizontalTabControl>
|
||||
</Grid>
|
||||
</DataTemplate>
|
||||
|
||||
<DataTemplate DataType="{x:Type pnl:PNLColumnSettingsVM}">
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="*"/>
|
||||
</Grid.RowDefinitions>
|
||||
<!--<Rectangle Grid.Row="1" HorizontalAlignment="Left" VerticalAlignment="Stretch" Width="{StaticResource CellTypesTabWidth}" Fill="#FFE8E8E8"/>-->
|
||||
<!--<TabControl
|
||||
Grid.Row="1"
|
||||
ItemsSource="{Binding cell_modes}"
|
||||
SelectedItem="{Binding selected_cell_mode}"
|
||||
Style="{StaticResource ParameterGroupTabControl}"
|
||||
/>-->
|
||||
<ContentControl Content="{Binding column_layout}" ContentTemplate="{StaticResource HeaderedParameterGroup}"/>
|
||||
<ContentControl Grid.Row="1" Content="{Binding fonts_and_colors}" ContentTemplate="{StaticResource HeaderedParameterGroup}"/>
|
||||
</Grid>
|
||||
</DataTemplate>
|
||||
<DataTemplate DataType="{x:Type sp:GridColumnSettingsVM}">
|
||||
<ListBox VerticalAlignment="Top" MinWidth="426" ItemsSource="{Binding parameters}" Style="{StaticResource NakedListBox}">
|
||||
<ListBox.ItemContainerStyle>
|
||||
<Style TargetType="ListBoxItem" BasedOn="{StaticResource NakedListBoxItem}">
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="ListBoxItem">
|
||||
<Border Background="Transparent">
|
||||
<ContentPresenter HorizontalAlignment="Stretch" VerticalAlignment="Stretch"/>
|
||||
</Border>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
</ListBox.ItemContainerStyle>
|
||||
</ListBox>
|
||||
</DataTemplate>
|
||||
<DataTemplate x:Key="HeaderedCellAppearanceContent" DataType="{x:Type sp:GridColumnSettingsVM}">
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="36"/>
|
||||
<RowDefinition Height="*"/>
|
||||
</Grid.RowDefinitions>
|
||||
<TextBlock VerticalAlignment="Center" FontSize="14" FontWeight="SemiBold">
|
||||
Cell Appearance
|
||||
</TextBlock>
|
||||
<ListBox Grid.Row="1" VerticalAlignment="Top" ItemsSource="{Binding parameters}" Style="{StaticResource NakedListBox}">
|
||||
<ListBox.ItemContainerStyle>
|
||||
<Style TargetType="ListBoxItem" BasedOn="{StaticResource NakedListBoxItem}">
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="ListBoxItem">
|
||||
<Border Background="Transparent">
|
||||
<ContentPresenter HorizontalAlignment="Stretch" VerticalAlignment="Stretch"/>
|
||||
</Border>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
</ListBox.ItemContainerStyle>
|
||||
</ListBox>
|
||||
</Grid>
|
||||
</DataTemplate>
|
||||
</ResourceDictionary>
|
||||
</UserControl.Resources>
|
||||
<UserControl.ContentTemplate>
|
||||
<DataTemplate DataType="{x:Type pnl:PNLSettingsVM}">
|
||||
<Grid>
|
||||
<Grid.Margin>
|
||||
<Thickness Left="{StaticResource HorizontalPush2}" Right="{StaticResource HorizontalPush2}"/>
|
||||
</Grid.Margin>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="*"/>
|
||||
</Grid.RowDefinitions>
|
||||
<c:ActionMenu
|
||||
Panel.ZIndex="1"
|
||||
HorizontalAlignment="Right"
|
||||
VerticalAlignment="Top"
|
||||
Margin="0,2,0,0"
|
||||
ItemsSource="{Binding DataContext.action_menu_items, RelativeSource={RelativeSource AncestorType=usp:ShellSettingsDialog}}"
|
||||
Style="{StaticResource SparkToolBarItem}"
|
||||
/>
|
||||
<TabControl x:Name="Tabs" Background="Transparent" HorizontalAlignment="Stretch" MinHeight="480" MinWidth="600" Margin="0,2,0,0" Padding="6">
|
||||
<TabControl.ItemContainerStyle>
|
||||
<Style TargetType="TabItem">
|
||||
<Setter Property="MinWidth" Value="60"/>
|
||||
<Setter Property="HorizontalAlignment" Value="Center"/>
|
||||
</Style>
|
||||
</TabControl.ItemContainerStyle>
|
||||
<TabItem Header="Grid">
|
||||
<ItemsControl x:Name="ParameterItemsControl" Grid.IsSharedSizeScope="True" Margin="8,16,0,0" ItemsSource="{Binding core_settings_vm.parameters}"/>
|
||||
</TabItem>
|
||||
<TabItem Header="Grouping">
|
||||
<GroupBox Grid.Column="3" Grid.RowSpan="3" HorizontalAlignment="Stretch" HorizontalContentAlignment="Stretch">
|
||||
<GroupBox.Resources>
|
||||
<u:CursorsEx/>
|
||||
</GroupBox.Resources>
|
||||
<GroupBox.Header>
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<TextBlock>Grouping</TextBlock>
|
||||
<c:HelpButton>
|
||||
<c:HelpButton.Resources>
|
||||
<Style x:Key="HelpButtonToolTipStyleKey" TargetType="ToolTip">
|
||||
<Setter Property="ContentTemplate">
|
||||
<Setter.Value>
|
||||
<DataTemplate>
|
||||
<TextBlock>
|
||||
<Run>Check the group parameters you would like to use.</Run>
|
||||
<LineBreak/>
|
||||
<Run FontStyle="Italic">Hint: drag/drop the items below to change the grouping hierarchy.</Run>
|
||||
</TextBlock>
|
||||
</DataTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
</c:HelpButton.Resources>
|
||||
</c:HelpButton>
|
||||
<ItemsControl ItemsSource="{Binding anchor_item_headers}" ItemsPanel="{StaticResource HorizontalItemsPanelTemplate}">
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<CheckBox Margin="4,0,0,0" Content="{Binding key}" ContentTemplate="{StaticResource EnumDescriptionTemplate}" IsChecked="{Binding include}"/>
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
</ItemsControl>
|
||||
</StackPanel>
|
||||
</GroupBox.Header>
|
||||
<ItemsControl
|
||||
HorizontalAlignment="Left"
|
||||
VerticalAlignment="Top"
|
||||
AllowDrop="True"
|
||||
ItemsSource="{Binding anchor_items}"
|
||||
ItemTemplate="{DynamicResource AnchorItemTemplate}"
|
||||
ItemsPanel="{StaticResource HorizontalItemsPanelTemplate}"
|
||||
Loaded="_pnl_anchor_items_loaded"
|
||||
/>
|
||||
</GroupBox>
|
||||
</TabItem>
|
||||
<TabItem Header="Columns">
|
||||
<Grid Grid.IsSharedSizeScope="True">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<!--<ColumnDefinition Width="{Binding Source={StaticResource HorizontalPush4}}"/>-->
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<c:DragDropListBox
|
||||
x:Name="ColumnListBox"
|
||||
DataContext="{Binding core_settings_vm}"
|
||||
SelectedItem="{Binding current_column}"
|
||||
SelectItemOnDrop="True"
|
||||
Style="{DynamicResource ColumnsListBox}"
|
||||
/>
|
||||
<!--<ListBox
|
||||
x:Name="CellTypeListBox"
|
||||
Grid.Column="1"
|
||||
Width="165"
|
||||
ItemsSource="{u:EnumBindingSource {x:Type pnl:CellTypes}}"
|
||||
SelectedIndex="0"
|
||||
Style="{StaticResource LargeMenuListBox}"
|
||||
>
|
||||
<ListBox.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<TextBlock Text="{Binding StringFormat='{}{0} Cell Appearance'}"/>
|
||||
</DataTemplate>
|
||||
</ListBox.ItemTemplate>
|
||||
<ListBox.Background>
|
||||
<SolidColorBrush Color="#FFDDDDDD"/>
|
||||
</ListBox.Background>
|
||||
<ListBox.ItemContainerStyle>
|
||||
<Style TargetType="ListBoxItem" BasedOn="{StaticResource LargeMenuListBoxItemBase}">
|
||||
<Setter Property="Padding" Value="8"/>
|
||||
<Setter Property="FontSize" Value="13"/>
|
||||
<Setter Property="FontWeight" Value="{Binding IsSelected, RelativeSource={RelativeSource Self}, Converter={StaticResource BooleanFontWeightConverter}}"/>
|
||||
</Style>
|
||||
</ListBox.ItemContainerStyle>
|
||||
</ListBox>-->
|
||||
<GridSplitter Grid.Column="1" HorizontalAlignment="Left" VerticalAlignment="Stretch" Width="5" Background="Transparent"/>
|
||||
<ScrollViewer Grid.Column="2" VerticalAlignment="Stretch" VerticalScrollBarVisibility="Auto" Content="{Binding core_settings_vm.current_column}">
|
||||
<!--<MultiBinding Converter="{x:Static usp:PNLSettingsView.ColumnContentConverter}">
|
||||
<Binding Path="SelectedItem" ElementName="ColumnListBox"/>
|
||||
<Binding Path="SelectedItem" ElementName="CellTypeListBox"/>
|
||||
</MultiBinding>-->
|
||||
<ScrollViewer.MaxHeight>
|
||||
<Binding Path="ActualHeight" RelativeSource="{RelativeSource AncestorType=usp:PNLSettingsView}">
|
||||
<Binding.Converter>
|
||||
<cv:DoubleSubtractionConverter Value="26"/>
|
||||
</Binding.Converter>
|
||||
</Binding>
|
||||
</ScrollViewer.MaxHeight>
|
||||
</ScrollViewer>
|
||||
</Grid>
|
||||
</TabItem>
|
||||
</TabControl>
|
||||
</Grid>
|
||||
</DataTemplate>
|
||||
</UserControl.ContentTemplate>
|
||||
</UserControl>
|
||||
@@ -0,0 +1,70 @@
|
||||
using bts.shell.pnl;
|
||||
using mbts.spark;
|
||||
using mbts.ui.converters;
|
||||
using mbts.ui.viewmodels;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Data;
|
||||
|
||||
namespace mbts.ui.spark {
|
||||
/// <summary>
|
||||
/// Interaction logic for PNLSettingsView.xaml
|
||||
/// </summary>
|
||||
partial class PNLSettingsView : 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 static IMultiValueConverter
|
||||
ColumnContentConverter => ValueConverter.create<PNLColumnSettingsVM, CellTypes, GridColumnSettingsVM>((col, cell) => {
|
||||
if (col != null) {
|
||||
switch (cell) {
|
||||
case CellTypes.Row: return col;
|
||||
case CellTypes.Group: return col.group_column_vm;
|
||||
case CellTypes.Header: return col.header_column_vm;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
});
|
||||
|
||||
public PNLSettingsView() =>
|
||||
InitializeComponent();
|
||||
|
||||
private void _pnl_anchor_items_loaded(object sender, System.Windows.RoutedEventArgs e) {
|
||||
|
||||
}
|
||||
|
||||
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}";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class PNLModalColumnSettingsVM {
|
||||
public PNLColumnSettingsVM column { get; }
|
||||
}
|
||||
|
||||
class PNLParameterContentTemplateSelector : DataTemplateSelector {
|
||||
public DataTemplate ParameterList { get; set; }
|
||||
public DataTemplate TabGroups { get; set; }
|
||||
|
||||
public override DataTemplate SelectTemplate(object item, DependencyObject container) {
|
||||
if (item is ParameterGroupVM g && g.enable_tab_display)
|
||||
return TabGroups;
|
||||
return ParameterList;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
using bts.api;
|
||||
using bts.api.orderman;
|
||||
using bts.shell;
|
||||
using bts.utils;
|
||||
using ProtoBuf;
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
|
||||
namespace mbts {
|
||||
[ProtoContract]
|
||||
class OrderSentEventPredicate : IEquatable<OrderSentEventPredicate> {
|
||||
public enum PredicateTypeOneofCase {
|
||||
[Description("(any order)")]
|
||||
AnyOrder,
|
||||
[Description("choose securities")]
|
||||
SecurityOrder
|
||||
}
|
||||
|
||||
[ProtoMember(1)]
|
||||
public SecurityPredicate security { get; set; }
|
||||
|
||||
public PredicateTypeOneofCase predicate_type_case {
|
||||
get {
|
||||
if (security != null)
|
||||
return PredicateTypeOneofCase.SecurityOrder;
|
||||
return PredicateTypeOneofCase.AnyOrder;
|
||||
}
|
||||
}
|
||||
|
||||
public IPredicate<OrderSecurity> create(object obj) =>
|
||||
create_predicate((INotificationServiceDataCache)obj);
|
||||
|
||||
public IPredicate<OrderSecurity> create_predicate(INotificationServiceDataCache data_cache) =>
|
||||
OrderEventPredicateHelper.create_status_transition_predicate(OrderStatus.Working, security, data_cache);
|
||||
|
||||
public INotificationPredicate<OrderSecurity> create_toast(INotificationServiceDataCache data_cache) =>
|
||||
new MyToaster(security?.create(), data_cache);
|
||||
|
||||
public bool Equals(OrderSentEventPredicate other) => other != null && other.security.safe_equals(other.security);
|
||||
public override bool Equals(object obj) => Equals(obj as OrderSentEventPredicate);
|
||||
public override int GetHashCode() => security == null ? 0 : security.GetHashCode();
|
||||
|
||||
static bool _is_canceled(OrderRecordImmutable o) =>
|
||||
o.HasState && o.State.Status == OrderStatus.Working;
|
||||
|
||||
class MyToaster : INotificationPredicate<OrderSecurity>, IEquatable<MyToaster> {
|
||||
readonly IPredicate<SecurityImmutable> _security_predicate;
|
||||
readonly INotificationServiceDataCache _data_cache;
|
||||
|
||||
public MyToaster(IPredicate<SecurityImmutable> security_predicate, INotificationServiceDataCache data_cache) {
|
||||
_security_predicate = security_predicate;
|
||||
_data_cache = data_cache;
|
||||
}
|
||||
|
||||
public IToastNotification trigger(OrderSecurity order) =>
|
||||
OrderEventPredicateHelper.check_order_transition_trigger(order, _is_canceled, _security_predicate, _data_cache, out var oldval) ?
|
||||
ToastNotifications.order_status_changed(order.order.Key,
|
||||
OrderEventPredicateHelper.get_nullable_status(oldval), OrderStatus.Working, order.security) :
|
||||
ToastNotifications.empty;
|
||||
|
||||
bool IPredicate<OrderSecurity>.trigger(OrderSecurity order) =>
|
||||
OrderEventPredicateHelper.check_order_transition_predicate(order, _is_canceled, _security_predicate, _data_cache);
|
||||
|
||||
public bool Equals(MyToaster other) =>
|
||||
other != null && EqualityComparer.null_check_equals(_security_predicate, other._security_predicate);
|
||||
|
||||
public override bool Equals(object obj) =>
|
||||
Equals(obj as MyToaster);
|
||||
|
||||
public override int GetHashCode() =>
|
||||
EqualityComparer.aggregate_hash((int)OrderStatus.Working, _security_predicate == null ? 0 : _security_predicate.GetHashCode());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
@@ -77,6 +27,7 @@ namespace mbts {
|
||||
OrderFilledEventPredicate _filled_event;
|
||||
OrderCompletedEventPredicate _completed_event;
|
||||
OrderCanceledEventPredicate _canceled_event;
|
||||
OrderSentEventPredicate _sent_event;
|
||||
|
||||
public enum PredicateTypeOneofCase {
|
||||
None,
|
||||
@@ -120,12 +71,14 @@ 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("Order Sent", PredicateCategory.Event)]
|
||||
SentEvent,
|
||||
[PredicateCase(is_toastable:false)]
|
||||
AttachedTargetOrderKey
|
||||
}
|
||||
@@ -176,6 +129,8 @@ namespace mbts {
|
||||
public OrderCompletedEventPredicate completed_event { get => _completed_event; set => _set(ref _completed_event, value); }
|
||||
[ProtoMember(23)]
|
||||
public OrderCanceledEventPredicate canceled_event { get => _canceled_event; set => _set(ref _canceled_event, value); }
|
||||
[ProtoMember(26)]
|
||||
public OrderSentEventPredicate sent_event { get => _sent_event; set => _set(ref _sent_event, value); }
|
||||
[ProtoMember(24)]
|
||||
public bool is_toast_enabled { get; set; }
|
||||
[ProtoMember(25)]
|
||||
@@ -206,6 +161,7 @@ namespace mbts {
|
||||
if (_filled_event != null) return PTOC.FilledEvent;
|
||||
if (_completed_event != null) return PTOC.CompletedEvent;
|
||||
if (_canceled_event != null) return PTOC.CanceledEvent;
|
||||
if (_sent_event != null) return PTOC.SentEvent;
|
||||
if (_attached_target_order_key != null) return PTOC.AttachedTargetOrderKey;
|
||||
return PTOC.None;
|
||||
}
|
||||
@@ -236,6 +192,7 @@ namespace mbts {
|
||||
case PTOC.FilledEvent: return _filled_event.create_predicate(data_cache);
|
||||
case PTOC.CompletedEvent: return _completed_event.create_predicate(data_cache);
|
||||
case PTOC.CanceledEvent: return _canceled_event.create(data_cache);
|
||||
case PTOC.SentEvent: return _sent_event.create(data_cache);
|
||||
case PTOC.AttachedTargetOrderKey: return wrap(_attached_target_order_key.create(), r => r.order.attached_target_order_key());
|
||||
default: return null;
|
||||
}
|
||||
@@ -285,6 +242,7 @@ namespace mbts {
|
||||
case PTOC.FilledEvent: return _filled_event.Equals(other._filled_event);
|
||||
case PTOC.CompletedEvent: return _completed_event.Equals(other._completed_event);
|
||||
case PTOC.CanceledEvent: return _canceled_event.Equals(other._canceled_event);
|
||||
case PTOC.SentEvent: return _sent_event.Equals(other._sent_event);
|
||||
case PTOC.AttachedTargetOrderKey: return _attached_target_order_key.Equals(other._attached_target_order_key);
|
||||
case PTOC.None: return true;
|
||||
default: throw ExceptionHelper.unexpected_oneof_case(ptoc);
|
||||
@@ -320,6 +278,7 @@ namespace mbts {
|
||||
case PTOC.FilledEvent: raw_hash = _filled_event.GetHashCode(); break;
|
||||
case PTOC.CompletedEvent: raw_hash = _completed_event.GetHashCode(); break;
|
||||
case PTOC.CanceledEvent: raw_hash = _canceled_event.GetHashCode(); break;
|
||||
case PTOC.SentEvent: raw_hash = _sent_event.GetHashCode(); break;
|
||||
case PTOC.AttachedTargetOrderKey: raw_hash = _attached_target_order_key.GetHashCode(); break;
|
||||
case PTOC.None: raw_hash = 0; break;
|
||||
default: throw ExceptionHelper.unexpected_oneof_case(ptoc);
|
||||
@@ -355,6 +314,7 @@ namespace mbts {
|
||||
_filled_event = null;
|
||||
_completed_event = null;
|
||||
_canceled_event = null;
|
||||
_sent_event = null;
|
||||
_attached_target_order_key = null;
|
||||
}
|
||||
field = value;
|
||||
@@ -363,6 +323,7 @@ namespace mbts {
|
||||
|
||||
[ProtoContract]
|
||||
class OrderRecordTuplePredicateAnimations : IPredicateAnimationsData<OrderSecurity, OrderRecordTuplePredicate> {
|
||||
const bool _default_is_enabled = true;
|
||||
[ProtoMember(1)]
|
||||
OrderRecordTuplePredicate _predicate;
|
||||
[ProtoMember(2)]
|
||||
@@ -371,19 +332,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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -44,6 +44,8 @@
|
||||
<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 +561,4 @@
|
||||
<Setter Property="Command" Value="{Binding command}"/>
|
||||
<Setter Property="Icon" Value="{Binding icon, Converter={StaticResource IconConverter}}"/>
|
||||
</Style>
|
||||
</ResourceDictionary>
|
||||
</ResourceDictionary>
|
||||
@@ -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,44 @@ 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 IDisposable copy<T>(this IProperty<T> target, IProperty<T> source, string property_name) =>
|
||||
// target.copy(source, property_name, property_name);
|
||||
|
||||
public static IDisposable copy<T>(this IProperty<T> target, IProperty<T> source) {
|
||||
//var target_prop = target.GetType().GetProperty(target_property_name);
|
||||
source.Changed += on_changed;
|
||||
return Disposable.Create(() => source.Changed -= on_changed);
|
||||
void on_changed(T oldval, T newval) {
|
||||
target.Value = newval;
|
||||
}
|
||||
}
|
||||
|
||||
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 +91,8 @@ 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 IDisposable S
|
||||
|
||||
public static IProperty<T> block_reentrancy<T>(this IProperty<T> source) =>
|
||||
new GatedPropertyWrapper<T>(source);
|
||||
|
||||
@@ -89,6 +129,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,19 @@
|
||||
xmlns:ui="clr-namespace:mbts.ui"
|
||||
>
|
||||
<!--Control Base Colors-->
|
||||
<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.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="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}" />
|
||||
|
||||
@@ -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>
|
||||
<Border Background="{TemplateBinding Background}">
|
||||
<Grid Background="Transparent">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
|
||||
@@ -1,11 +1,19 @@
|
||||
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)));
|
||||
}
|
||||
|
||||
@@ -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,144 @@
|
||||
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);
|
||||
Resources["SelectedOpacity"] = Model.opacity;
|
||||
Model.PropertyChanged += (s, e) => {
|
||||
if (e.PropertyName == nameof(Model.opacity))
|
||||
Resources["SelectedOpacity"] = Model.opacity;
|
||||
};
|
||||
//Loaded += on_loaded;
|
||||
//void on_loaded(object sender, RoutedEventArgs e) =>
|
||||
// Model.initialize();
|
||||
}
|
||||
|
||||
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>();
|
||||
|
||||
bool _is_initialized;
|
||||
public void initialize() {
|
||||
if (_is_initialized)
|
||||
return;
|
||||
_is_initialized = true;
|
||||
_owner.GetBindingExpression(SelectedColorProperty)?.UpdateTarget();
|
||||
_owner._color_picker.SelectedColorChanged += c => selected_color = c;
|
||||
}
|
||||
|
||||
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}" Opacity="{Binding SelectedOpacity, RelativeSource={RelativeSource AncestorType=c:TabularColorPicker}, Converter={x:Static c:TabularColorPicker.AlphaToOpacityConverter}}"/>
|
||||
</Border.BorderBrush>
|
||||
<Border.Background>
|
||||
<SolidColorBrush Color="{Binding color}" Opacity="{Binding SelectedOpacity, RelativeSource={RelativeSource AncestorType=c:TabularColorPicker}, Converter={x:Static c:TabularColorPicker.AlphaToOpacityConverter}}"/>
|
||||
</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,80 @@
|
||||
using bts.collections;
|
||||
using bts.proto;
|
||||
using bts.utils;
|
||||
using mbts.spark;
|
||||
using mbts.ui.controls.color_picker;
|
||||
using mbts.ui.converters;
|
||||
using mbts.ui.shell;
|
||||
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.Linq;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Controls.Primitives;
|
||||
using System.Windows.Data;
|
||||
using System.Windows.Media;
|
||||
|
||||
namespace mbts.ui.controls {
|
||||
/// <summary>
|
||||
/// Interaction logic for ColorPickerPopup.xaml
|
||||
/// </summary>
|
||||
class TabularColorPicker : ListBox {
|
||||
public static readonly IValueConverter AlphaToOpacityConverter =
|
||||
ValueConverter.create<byte, double>(alpha => (double)alpha / 255.0);
|
||||
|
||||
static TabularColorPicker() =>
|
||||
DefaultStyleKeyProperty.OverrideMetadata(typeof(TabularColorPicker), new FrameworkPropertyMetadata(typeof(TabularColorPicker)));
|
||||
|
||||
//public static readonly DependencyProperty SelectedColorProperty =
|
||||
// WPFHelper.create_dp<Color, TabularColorPicker>(
|
||||
// nameof(SelectedColor),
|
||||
// FrameworkPropertyMetadataOptions.BindsTwoWayByDefault,
|
||||
// Colors.Black);
|
||||
|
||||
//public Color SelectedColor {
|
||||
// get => (Color)GetValue(SelectedColorProperty);
|
||||
// set => SetValue(SelectedColorProperty, value);
|
||||
//}
|
||||
//protected override DependencyObject GetContainerForItemOverride() =>
|
||||
// new TabularColorPickerItem();
|
||||
|
||||
public static readonly DependencyProperty SelectedOpacityProperty =
|
||||
WPFHelper.create_dp<byte, TabularColorPicker>(nameof(SelectedOpacity), 255);
|
||||
|
||||
public byte SelectedOpacity {
|
||||
get => (byte)GetValue(SelectedOpacityProperty);
|
||||
set => SetValue(SelectedOpacityProperty, value);
|
||||
}
|
||||
|
||||
//protected override DependencyObject GetContainerForItemOverride() =>
|
||||
// new ListBoxItem();
|
||||
}
|
||||
|
||||
class TabularColorPickerItem : ListBoxItem {
|
||||
static TabularColorPickerItem() =>
|
||||
DefaultStyleKeyProperty.OverrideMetadata(typeof(TabularColorPickerItem), new FrameworkPropertyMetadata(typeof(TabularColorPickerItem)));
|
||||
|
||||
public static readonly DependencyProperty ColorProperty =
|
||||
WPFHelper.create_dp<Color, TabularColorPickerItem>(nameof(Color));
|
||||
|
||||
public Color Color {
|
||||
get => (Color)GetValue(ColorProperty);
|
||||
set => SetValue(ColorProperty, value);
|
||||
}
|
||||
|
||||
public static readonly DependencyProperty ColorNameProperty =
|
||||
WPFHelper.create_dp<string, TabularColorPickerItem>(nameof(ColorName));
|
||||
|
||||
public string ColorName {
|
||||
get => GetValue(ColorNameProperty) as string;
|
||||
set => SetValue(ColorNameProperty, 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;
|
||||
}
|
||||
|
||||
@@ -98,9 +98,10 @@ namespace mbts.ui.settings {
|
||||
|
||||
public GridColumnSettings with(
|
||||
string display_name = null, bool? is_visible = null, bool? is_collapsed = null, NullableFontWeightProto? font_weight_override = null, NullableColorProto? font_color_override = null,
|
||||
double? width = null, HorizontalAlignment? horizontal_alignment = null, double? horizontal_offset = null, NullableColorProto? background_color_override = null) =>
|
||||
double? width = null, HorizontalAlignment? horizontal_alignment = null, double? horizontal_offset = null, NullableColorProto? background_color_override = null,
|
||||
string id = null) =>
|
||||
//
|
||||
new GridColumnSettings(_id,
|
||||
new GridColumnSettings(id ?? _id,
|
||||
display_name ?? this.display_name,
|
||||
is_visible ?? this.is_visible,
|
||||
is_collapsed ?? this.is_collapsed,
|
||||
|
||||
@@ -1,18 +1,13 @@
|
||||
using bts.collections;
|
||||
using bts.proto;
|
||||
using mbts.collections;
|
||||
using bts.proto;
|
||||
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.Collections.ObjectModel;
|
||||
using System.Windows;
|
||||
using System.Windows.Media;
|
||||
using CellAnimation = mbts.ui.CellAnimation;
|
||||
|
||||
namespace mbts.spark {
|
||||
/// <summary>
|
||||
@@ -28,172 +23,122 @@ 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 : DisposableViewModelBase {
|
||||
string _id = string.Empty;
|
||||
GridColumnSettings _settings;
|
||||
|
||||
public GridColumnSettingsVM()
|
||||
: this(GridColumnSettingsFeatureFlags.None) { }
|
||||
|
||||
protected GridColumnSettingsVM(GridColumnSettingsFeatureFlags? init_flags) {
|
||||
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");
|
||||
// display name
|
||||
public ObservableCollection<object> parameters { get; } = new ObservableCollection<object>();
|
||||
/// Note: parameter visibility is not automatically re-evaluated (<see cref="sync_params"/>).
|
||||
public bool show_width { get; set; } = true;
|
||||
public bool show_horizontal_alignment { get; set; } = true;
|
||||
public bool can_edit_header { get; set; } = true;
|
||||
public bool can_edit_width { get; set; } = true;
|
||||
public bool can_edit_horizontal_alignment { get; set; } = true;
|
||||
|
||||
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 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;
|
||||
}));
|
||||
protected virtual IEnumerable<object> _visible_parameters {
|
||||
get {
|
||||
if (header.can_override)
|
||||
yield return header;
|
||||
if (show_width)
|
||||
yield return width;
|
||||
if (show_horizontal_alignment)
|
||||
yield return horizontal_alignment;
|
||||
if (background.can_override)
|
||||
yield return background;
|
||||
if (font_color.can_override)
|
||||
yield return font_color;
|
||||
if (font_weight.can_override)
|
||||
yield return font_weight;
|
||||
}
|
||||
}
|
||||
|
||||
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 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;
|
||||
parameters.sync_with(_visible_parameters);
|
||||
}
|
||||
|
||||
public void sync_params() =>
|
||||
parameters.sync_with(_visible_parameters);
|
||||
|
||||
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,22 @@
|
||||
using bts.api;
|
||||
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;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.ComponentModel;
|
||||
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 +44,52 @@ 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> {
|
||||
class GridSettingsVM : DisposableViewModelBase, IRequestViewClose, IWindowSettingsVM<GridSettings>, ITemplateSelectorKey {
|
||||
//
|
||||
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;
|
||||
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;
|
||||
readonly ObservableCollection<object> _parameters = new ObservableCollection<object>();
|
||||
bool _background_enabled, _show_column_display_name;
|
||||
|
||||
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) {
|
||||
public GridSettingsVM(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;
|
||||
_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));
|
||||
_parameters.sync_with(_visible_parameters);
|
||||
// font
|
||||
_dispose = ObservableAutoWrapper.connect_item_hooks(columns, c => {
|
||||
_font_vm.PropertyChanged += on_prop_changed;
|
||||
_background.PropertyChanged += on_prop_changed;
|
||||
_font_color.Changed += on_font_color_changed;
|
||||
on_font_color_changed(default, _font_color.value);
|
||||
on_prop_changed(null, null);
|
||||
return Disposable.Create(() => {
|
||||
_font_vm.PropertyChanged -= on_prop_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_prop_changed(object sender, PropertyChangedEventArgs e) {
|
||||
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 IReadOnlyList<DialogSkirtItem> dialog_skirt_items => new[] {
|
||||
new DialogSkirtItem("OK", new DelegateCommand(() => {
|
||||
committed?.Invoke();
|
||||
@@ -117,119 +98,81 @@ namespace mbts.spark {
|
||||
new DialogSkirtItem("Cancel", new DelegateCommand(() => RequestClose?.Invoke(this, new EventArgs()))) { is_cancel = true },
|
||||
new DialogSkirtItem("Apply", new DelegateCommand(() => committed?.Invoke()))
|
||||
};
|
||||
public bool show_column_display_name {
|
||||
get => _show_column_display_name;
|
||||
set => Set(ref _show_column_display_name, value);
|
||||
}
|
||||
|
||||
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 IEnumerable parameters => _parameters;
|
||||
|
||||
IEnumerable<object> _visible_parameters {
|
||||
get {
|
||||
yield return _selection_highlight;
|
||||
if (_background_enabled)
|
||||
yield return _background;
|
||||
yield return _font_vm;
|
||||
yield return _font_color;
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
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 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 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 (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);
|
||||
}
|
||||
_parameters.sync_with(_visible_parameters);
|
||||
}
|
||||
}
|
||||
}
|
||||
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;
|
||||
|
||||
@@ -0,0 +1,175 @@
|
||||
<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:usp="clr-namespace:mbts.ui.spark"
|
||||
xmlns:uspcv="clr-namespace:mbts.ui.spark.converters"
|
||||
xmlns:txt="clr-namespace:mbts.ui.utility.text"
|
||||
>
|
||||
<!--Control colors.-->
|
||||
<Color x:Key="WindowColor">#FFE8EDF9</Color>
|
||||
<Color x:Key="ContentAreaColorLight">#FFC5CBF9</Color>
|
||||
<Color x:Key="ContentAreaColorDark">#FF7381F9</Color>
|
||||
|
||||
<Color x:Key="DisabledControlLightColor">#FFE8EDF9</Color>
|
||||
<Color x:Key="DisabledControlDarkColor">#FFC5CBF9</Color>
|
||||
<Color x:Key="DisabledForegroundColor">#FF888888</Color>
|
||||
|
||||
<Color x:Key="SelectedBackgroundColor">#FFC5CBF9</Color>
|
||||
<Color x:Key="SelectedUnfocusedColor">#FFDDDDDD</Color>
|
||||
|
||||
<Color x:Key="ControlLightColor">White</Color>
|
||||
<Color x:Key="ControlMediumColor">#FF7381F9</Color>
|
||||
<Color x:Key="ControlDarkColor">#FF211AA9</Color>
|
||||
|
||||
<Color x:Key="ControlMouseOverColor">#FF3843C4</Color>
|
||||
<Color x:Key="ControlPressedColor">#FF211AA9</Color>
|
||||
|
||||
|
||||
<Color x:Key="GlyphColor">#FF444444</Color>
|
||||
<Color x:Key="GlyphMouseOver">sc#1, 0.004391443, 0.002428215, 0.242281124</Color>
|
||||
|
||||
<!--Border colors-->
|
||||
<Color x:Key="BorderLightColor">#FFCCCCCC</Color>
|
||||
<Color x:Key="BorderMediumColor">#FF888888</Color>
|
||||
<Color x:Key="BorderDarkColor">#FF444444</Color>
|
||||
|
||||
<Color x:Key="PressedBorderLightColor">#FF888888</Color>
|
||||
<Color x:Key="PressedBorderDarkColor">#FF444444</Color>
|
||||
|
||||
<Color x:Key="DisabledBorderLightColor">#FFAAAAAA</Color>
|
||||
<Color x:Key="DisabledBorderDarkColor">#FF888888</Color>
|
||||
|
||||
<Style x:Key="FlatTabItem" TargetType="{x:Type TabItem}">
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="{x:Type TabItem}">
|
||||
<Grid x:Name="Root">
|
||||
<VisualStateManager.VisualStateGroups>
|
||||
<VisualStateGroup x:Name="SelectionStates">
|
||||
<VisualState x:Name="Unselected" />
|
||||
<VisualState x:Name="Selected">
|
||||
<Storyboard>
|
||||
<ColorAnimationUsingKeyFrames Storyboard.TargetName="Border" Storyboard.TargetProperty="(Panel.Background).(GradientBrush.GradientStops)[1].(GradientStop.Color)">
|
||||
<EasingColorKeyFrame KeyTime="0" Value="{StaticResource ControlPressedColor}"/>
|
||||
</ColorAnimationUsingKeyFrames>
|
||||
<ThicknessAnimationUsingKeyFrames Storyboard.TargetProperty="(Border.BorderThickness)" Storyboard.TargetName="Border">
|
||||
<EasingThicknessKeyFrame KeyTime="0" Value="1,1,1,0"/>
|
||||
</ThicknessAnimationUsingKeyFrames>
|
||||
</Storyboard>
|
||||
</VisualState>
|
||||
</VisualStateGroup>
|
||||
<VisualStateGroup x:Name="CommonStates">
|
||||
<VisualState x:Name="Normal" />
|
||||
<VisualState x:Name="MouseOver" />
|
||||
<VisualState x:Name="Disabled">
|
||||
<Storyboard>
|
||||
<ColorAnimationUsingKeyFrames Storyboard.TargetName="Border" Storyboard.TargetProperty="(Panel.Background).(GradientBrush.GradientStops)[1].(GradientStop.Color)">
|
||||
<EasingColorKeyFrame KeyTime="0" Value="{StaticResource DisabledControlDarkColor}"/>
|
||||
</ColorAnimationUsingKeyFrames>
|
||||
<ColorAnimationUsingKeyFrames Storyboard.TargetName="Border" Storyboard.TargetProperty="(Border.BorderBrush).(SolidColorBrush.Color)">
|
||||
<EasingColorKeyFrame KeyTime="0" Value="{StaticResource DisabledBorderLightColor}"/>
|
||||
</ColorAnimationUsingKeyFrames>
|
||||
</Storyboard>
|
||||
</VisualState>
|
||||
</VisualStateGroup>
|
||||
</VisualStateManager.VisualStateGroups>
|
||||
<Border
|
||||
x:Name="Border"
|
||||
Margin="0,0,-4,0"
|
||||
BorderThickness="1"
|
||||
>
|
||||
<Border.BorderBrush>
|
||||
<SolidColorBrush Color="{DynamicResource BorderMediumColor}" />
|
||||
</Border.BorderBrush>
|
||||
<Border.Background>
|
||||
<LinearGradientBrush StartPoint="0,0" EndPoint="0,1">
|
||||
<LinearGradientBrush.GradientStops>
|
||||
<GradientStopCollection>
|
||||
<GradientStop Color="{DynamicResource ControlLightColor}" Offset="0.0"/>
|
||||
<GradientStop Color="{DynamicResource ControlMediumColor}" Offset="1.0"/>
|
||||
</GradientStopCollection>
|
||||
</LinearGradientBrush.GradientStops>
|
||||
</LinearGradientBrush>
|
||||
</Border.Background>
|
||||
<ContentPresenter
|
||||
x:Name="ContentSite"
|
||||
VerticalAlignment="Center"
|
||||
HorizontalAlignment="Center"
|
||||
ContentSource="Header"
|
||||
Margin="12,2,12,2"
|
||||
RecognizesAccessKey="True"
|
||||
/>
|
||||
</Border>
|
||||
</Grid>
|
||||
<ControlTemplate.Triggers>
|
||||
<Trigger Property="IsSelected" Value="True">
|
||||
<Setter Property="Panel.ZIndex" Value="100"/>
|
||||
</Trigger>
|
||||
</ControlTemplate.Triggers>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
|
||||
<Style TargetType="{x:Type c:HorizontalTabControl}">
|
||||
<Setter Property="OverridesDefaultStyle" Value="True"/>
|
||||
<Setter Property="SnapsToDevicePixels" Value="True"/>
|
||||
<Setter Property="ItemContainerStyle" Value="{StaticResource FlatTabItem}"/>
|
||||
<Setter Property="ItemsPanel">
|
||||
<Setter.Value>
|
||||
<ItemsPanelTemplate>
|
||||
<StackPanel Orientation="Horizontal"/>
|
||||
</ItemsPanelTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="{x:Type c:HorizontalTabControl}">
|
||||
<Grid KeyboardNavigation.TabNavigation="Local">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="*" />
|
||||
</Grid.RowDefinitions>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<VisualStateManager.VisualStateGroups>
|
||||
<VisualStateGroup x:Name="CommonStates">
|
||||
<VisualState x:Name="Disabled">
|
||||
<Storyboard>
|
||||
<ColorAnimationUsingKeyFrames Storyboard.TargetName="Border" Storyboard.TargetProperty="(Border.BorderBrush).(SolidColorBrush.Color)">
|
||||
<EasingColorKeyFrame KeyTime="0" Value="#FFAAAAAA"/>
|
||||
</ColorAnimationUsingKeyFrames>
|
||||
</Storyboard>
|
||||
</VisualState>
|
||||
</VisualStateGroup>
|
||||
</VisualStateManager.VisualStateGroups>
|
||||
<ItemsPresenter
|
||||
x:Name="HeaderPanel"
|
||||
Grid.Row="0"
|
||||
Panel.ZIndex="1"
|
||||
Margin="0,0,4,-1"
|
||||
KeyboardNavigation.TabIndex="1"
|
||||
>
|
||||
</ItemsPresenter>
|
||||
<ContentPresenter
|
||||
x:Name="PART_SelectedContentHost"
|
||||
Grid.Row="1"
|
||||
Grid.ColumnSpan="2"
|
||||
KeyboardNavigation.TabNavigation="Local"
|
||||
KeyboardNavigation.DirectionalNavigation="Contained"
|
||||
KeyboardNavigation.TabIndex="2"
|
||||
Margin="4"
|
||||
ContentSource="SelectedContent"
|
||||
/>
|
||||
<!--<Rectangle Grid.Column="1" HorizontalAlignment="Stretch" VerticalAlignment="Top" Height="1" Fill="{TemplateBinding BorderBrush}"/>-->
|
||||
<Rectangle Grid.Column="1" HorizontalAlignment="Stretch" VerticalAlignment="Bottom" Margin="-4,0,0,-1" Height="1" Fill="DarkGray"/>
|
||||
</Grid>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
</ResourceDictionary>
|
||||
@@ -0,0 +1,24 @@
|
||||
using bts.api;
|
||||
using bts.utils;
|
||||
using mbts.spark;
|
||||
using mbts.ui.controls;
|
||||
using mbts.ui.extensions;
|
||||
using mbts.ui.utilities;
|
||||
using mbts.ui.utility;
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Controls.Primitives;
|
||||
using System.Windows.Input;
|
||||
|
||||
namespace mbts.ui.controls {
|
||||
/// <summary>
|
||||
/// Interaction logic for HorizontalTabControl.xaml
|
||||
/// </summary>
|
||||
class HorizontalTabControl : TabControl {
|
||||
static HorizontalTabControl() =>
|
||||
DefaultStyleKeyProperty.OverrideMetadata(typeof(HorizontalTabControl), new FrameworkPropertyMetadata(typeof(HorizontalTabControl)));
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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 {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -14,6 +14,15 @@ namespace mbts.ui {
|
||||
CommitAndRelease
|
||||
}
|
||||
|
||||
// RoutedEventHandlers
|
||||
public static RoutedEventHandler FocusTextBoxRoutedEventHandler => _focus_text_box_routed_event_handler;
|
||||
static void _focus_text_box_routed_event_handler(object sender, RoutedEventArgs e) {
|
||||
if (sender is TextBox tb || sender is FrameworkElement el && el.try_find_descendent_of_type(out tb)) {
|
||||
e.Handled = true;
|
||||
Keyboard.Focus(tb);
|
||||
}
|
||||
}
|
||||
|
||||
#region RoutedCommands/Handlers
|
||||
public static readonly RoutedCommand
|
||||
ReleaseCaptureCommand = new RoutedCommand(),
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
<ResourceDictionary
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:mbts="clr-namespace:mbts.ui"
|
||||
xmlns:u="clr-namespace:mbts.ui"
|
||||
xmlns:controls="clr-namespace:mbts.ui.controls">
|
||||
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:mbts="clr-namespace:mbts.ui"
|
||||
xmlns:u="clr-namespace:mbts.ui"
|
||||
xmlns:controls="clr-namespace:mbts.ui.controls"
|
||||
xmlns:cv="clr-namespace:mbts.ui.converters"
|
||||
>
|
||||
<SolidColorBrush x:Key="StartMenuMouseOverBorderBrush" Color="#FFFFB700" />
|
||||
<SolidColorBrush x:Key="StartMenuMouseOverBackground" Color="#6ddbff" />
|
||||
<SolidColorBrush x:Key="StartMenuPressedBorderBrush" Color="#FFC29B29" />
|
||||
@@ -22,6 +23,38 @@
|
||||
<SolidColorBrush x:Key="Button.MousePressed.BackgroundBrush" Color="{StaticResource Button.MousePressed.BackgroundColor}" />
|
||||
<SolidColorBrush x:Key="Button.MousePressed.BorderBrush" Color="{StaticResource Button.MousePressed.BorderColor}" />
|
||||
|
||||
<!--Storyboards/Timelines-->
|
||||
<!--Button Mouse Over-->
|
||||
<ObjectAnimationUsingKeyFrames x:Key="Button.MouseOver.BackgroundTimeline" Storyboard.TargetProperty="Background">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="{StaticResource Control.LightMediumGrayBrush}"/>
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
<ObjectAnimationUsingKeyFrames x:Key="Button.MouseOver.BorderTimeline" Storyboard.TargetProperty="BorderBrush">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="{mbts:BTSColor UI.Button.mouseover_background}"/>
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
<Storyboard x:Key="Button.MouseOver.Storyboard">
|
||||
<StaticResource ResourceKey="Button.MouseOver.BackgroundTimeline"/>
|
||||
<StaticResource ResourceKey="Button.MouseOver.BorderTimeline"/>
|
||||
</Storyboard>
|
||||
<!--Button Pressed-->
|
||||
<ObjectAnimationUsingKeyFrames x:Key="Button.Pressed.BackgroundTimeline" Storyboard.TargetProperty="Background">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="{mbts:BTSColor UI.Button.pressed_background}"/>
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
<ObjectAnimationUsingKeyFrames x:Key="Button.Pressed.BorderTimeline" Storyboard.TargetProperty="BorderBrush">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="{mbts:BTSColor UI.Button.pressed_background}"/>
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
<Storyboard x:Key="Button.Pressed.Storyboard">
|
||||
<StaticResource ResourceKey="Button.Pressed.BackgroundTimeline"/>
|
||||
<StaticResource ResourceKey="Button.Pressed.BorderTimeline"/>
|
||||
</Storyboard>
|
||||
|
||||
<!--ListBox MouseOver-->
|
||||
<ObjectAnimationUsingKeyFrames x:Key="ListBoxItem.MouseOver.BackgroundTimeline" Storyboard.TargetProperty="(Control.Background)">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="{StaticResource ListBoxItem.MouseOver.Background}"/>
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
<Storyboard x:Key="ListBoxItem.MouseOver.BackgroundStoryboard">
|
||||
<StaticResource ResourceKey="ListBoxItem.MouseOver.BackgroundTimeline"/>
|
||||
</Storyboard>
|
||||
|
||||
<Style x:Key="StartMenuButton" TargetType="{x:Type Button}">
|
||||
<Setter Property="Background" Value="Transparent" />
|
||||
<Setter Property="OverridesDefaultStyle" Value="True" />
|
||||
@@ -237,7 +270,6 @@
|
||||
</Setter>
|
||||
|
||||
</Style>
|
||||
|
||||
<!-- The Expander used in spark menus (Tool Bar) -->
|
||||
<Style x:Key="MenuExpanderButtonBase" TargetType="ToggleButton">
|
||||
<Setter Property="Background" Value="{mbts:BTSColor UI.spark_menu_background}"/>
|
||||
@@ -459,6 +491,35 @@
|
||||
</Setter>
|
||||
</Style>
|
||||
|
||||
<ControlTemplate x:Key="HelpButtonGenericTemplate" TargetType="{x:Type Control}">
|
||||
<Grid Width="{TemplateBinding Width}" Height="{TemplateBinding Height}">
|
||||
<Grid.ToolTip>
|
||||
<ToolTip Content="{TemplateBinding ToolTip}" Style="{DynamicResource HelpButtonToolTipStyleKey}"/>
|
||||
</Grid.ToolTip>
|
||||
<Ellipse HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Fill="{TemplateBinding Background}" Opacity="0" x:Name="_el" />
|
||||
<TextBlock Text="?" HorizontalAlignment="Center" VerticalAlignment="Center" />
|
||||
</Grid>
|
||||
<ControlTemplate.Triggers>
|
||||
<Trigger Property="IsMouseOver" Value="True">
|
||||
<Setter Property="Foreground" Value="White" />
|
||||
<Trigger.EnterActions>
|
||||
<BeginStoryboard>
|
||||
<Storyboard>
|
||||
<DoubleAnimation Storyboard.TargetName="_el" Storyboard.TargetProperty="Opacity" To="1" Duration="0:0:0.05" />
|
||||
</Storyboard>
|
||||
</BeginStoryboard>
|
||||
</Trigger.EnterActions>
|
||||
<Trigger.ExitActions>
|
||||
<BeginStoryboard>
|
||||
<Storyboard>
|
||||
<DoubleAnimation Storyboard.TargetName="_el" Storyboard.TargetProperty="Opacity" To="0" Duration="0:0:0.2" />
|
||||
</Storyboard>
|
||||
</BeginStoryboard>
|
||||
</Trigger.ExitActions>
|
||||
</Trigger>
|
||||
</ControlTemplate.Triggers>
|
||||
</ControlTemplate>
|
||||
|
||||
<Style TargetType="Button" BasedOn="{StaticResource RoundMinimizeButton}" x:Key="HelpButtonBaseStyle">
|
||||
<Setter Property="Background" Value="{x:Static mbts:BTSColors.yellow_brush}" />
|
||||
<Setter Property="ToolTip" Value="Click here for help" />
|
||||
@@ -595,6 +656,225 @@
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
|
||||
<Style x:Key="LargeMenuControlBase" TargetType="Control">
|
||||
<Setter Property="Background" Value="#F5F5F5"/>
|
||||
<Setter Property="BorderThickness" Value="0"/>
|
||||
<Setter Property="HorizontalAlignment" Value="Stretch"/>
|
||||
<Setter Property="HorizontalContentAlignment" Value="Left"/>
|
||||
<Setter Property="Padding" Value="6"/>
|
||||
</Style>
|
||||
|
||||
<Style x:Key="LargeMenuButton" TargetType="Button" BasedOn="{StaticResource LargeMenuControlBase}">
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="Button">
|
||||
<Border
|
||||
x:Name="Border"
|
||||
CornerRadius="0"
|
||||
BorderThickness="{TemplateBinding BorderThickness}"
|
||||
BorderBrush="{TemplateBinding BorderBrush}"
|
||||
Background="{TemplateBinding Background}"
|
||||
Padding="{TemplateBinding Padding}"
|
||||
Height="{TemplateBinding Height}"
|
||||
Width="{TemplateBinding Width}"
|
||||
SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"
|
||||
>
|
||||
<u:StateManager.Groups>
|
||||
<u:StateGroupsManager AttachToMembers="True">
|
||||
<u:StateDefinitionCollection>
|
||||
<u:MouseStateDefinition MouseSource="{Binding Group.Target.TemplatedParent, RelativeSource={RelativeSource Self}}"/>
|
||||
</u:StateDefinitionCollection>
|
||||
</u:StateGroupsManager>
|
||||
</u:StateManager.Groups>
|
||||
<VisualStateManager.VisualStateGroups>
|
||||
<VisualStateGroup Name="MouseStates">
|
||||
<VisualState Name="Normal"/>
|
||||
<VisualState Name="MouseOver" Storyboard="{StaticResource Button.MouseOver.Storyboard}"/>
|
||||
<VisualState Name="Pressed" Storyboard="{StaticResource Button.Pressed.Storyboard}"/>
|
||||
</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="LargeMenuPopupControl" TargetType="controls:PopupControl">
|
||||
<Setter Property="BorderThickness" Value="0"/>
|
||||
<Setter Property="HorizontalAlignment" Value="Stretch"/>
|
||||
<Setter Property="HorizontalContentAlignment" Value="Stretch"/>
|
||||
<Setter Property="VerticalContentAlignment" Value="Stretch"/>
|
||||
<Setter Property="Background" Value="Transparent"/>
|
||||
<Setter Property="ButtonStyle" Value="{StaticResource LargeMenuButton}"/>
|
||||
<Setter Property="ShowExpanderArrow" Value="False"/>
|
||||
</Style>
|
||||
|
||||
<Style x:Key="AddItemButton" TargetType="Button" BasedOn="{StaticResource LargeMenuButton}">
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="Button">
|
||||
<Border
|
||||
x:Name="Border"
|
||||
CornerRadius="0"
|
||||
BorderThickness="{TemplateBinding BorderThickness}"
|
||||
BorderBrush="{TemplateBinding BorderBrush}"
|
||||
Background="{TemplateBinding Background}"
|
||||
Padding="{TemplateBinding Padding}"
|
||||
Height="{TemplateBinding Height}"
|
||||
Width="{TemplateBinding Width}"
|
||||
SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"
|
||||
>
|
||||
<u:StateManager.Groups>
|
||||
<u:StateGroupsManager AttachToMembers="True">
|
||||
<u:StateDefinitionCollection>
|
||||
<u:MouseStateDefinition MouseSource="{Binding Group.Target.TemplatedParent, RelativeSource={RelativeSource Self}}"/>
|
||||
</u:StateDefinitionCollection>
|
||||
</u:StateGroupsManager>
|
||||
</u:StateManager.Groups>
|
||||
<VisualStateManager.VisualStateGroups>
|
||||
<VisualStateGroup Name="MouseStates">
|
||||
<VisualState Name="Normal"/>
|
||||
<VisualState Name="MouseOver" Storyboard="{StaticResource Button.MouseOver.Storyboard}"/>
|
||||
<VisualState Name="Pressed" Storyboard="{StaticResource Button.Pressed.Storyboard}"/>
|
||||
</VisualStateGroup>
|
||||
</VisualStateManager.VisualStateGroups>
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<Image Height="20" Source="{x:Static u:Icons.AddBox}"/>
|
||||
<ContentPresenter
|
||||
Name="presenter"
|
||||
Grid.Column="1"
|
||||
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
|
||||
VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
|
||||
Margin="8,0,0,0"
|
||||
RecognizesAccessKey="True"
|
||||
/>
|
||||
</Grid>
|
||||
</Border>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
|
||||
<ControlTemplate x:Key="LargeMenuListBoxItemTemplate" TargetType="ListBoxItem">
|
||||
<Border
|
||||
Background="{TemplateBinding Background}"
|
||||
BorderBrush="{TemplateBinding BorderBrush}"
|
||||
BorderThickness="{TemplateBinding BorderThickness}"
|
||||
HorizontalAlignment="{TemplateBinding HorizontalAlignment}"
|
||||
VerticalAlignment="{TemplateBinding VerticalAlignment}"
|
||||
Padding="{TemplateBinding Padding}"
|
||||
Height="{TemplateBinding Height}"
|
||||
Width="{TemplateBinding Width}"
|
||||
SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"
|
||||
>
|
||||
<u:StateManager.Groups>
|
||||
<u:StateGroupsManager AttachToMembers="True">
|
||||
<u:StateDefinitionCollection>
|
||||
<u:MouseStateDefinition MouseSource="{Binding Group.Target.TemplatedParent, RelativeSource={RelativeSource Self}}"/>
|
||||
</u:StateDefinitionCollection>
|
||||
</u:StateGroupsManager>
|
||||
</u:StateManager.Groups>
|
||||
<VisualStateManager.VisualStateGroups>
|
||||
<VisualStateGroup Name="MouseStates">
|
||||
<VisualState Name="Normal"/>
|
||||
<VisualState Name="MouseOver" Storyboard="{StaticResource Button.MouseOver.Storyboard}"/>
|
||||
<VisualState Name="Pressed" Storyboard="{StaticResource Button.Pressed.Storyboard}"/>
|
||||
</VisualStateGroup>
|
||||
</VisualStateManager.VisualStateGroups>
|
||||
<ContentPresenter
|
||||
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
|
||||
VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
|
||||
/>
|
||||
</Border>
|
||||
</ControlTemplate>
|
||||
|
||||
<DataTemplate x:Key="CheckableListBoxItemContentTemplate">
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<Path
|
||||
VerticalAlignment="Bottom"
|
||||
Margin="4,0,6,2"
|
||||
Fill="Black"
|
||||
Data="{Binding Source={StaticResource {x:Static u:Icons.CheckMarkGeometryKey}}, Converter={x:Static cv:ValueConverters.GeometryParser}}"
|
||||
>
|
||||
<Path.Visibility>
|
||||
<Binding Path="IsSelected" RelativeSource="{RelativeSource AncestorType=ListBoxItem}">
|
||||
<Binding.Converter>
|
||||
<cv:BooleanToObjectConverter ValueForFalse="{x:Static Visibility.Hidden}" ValueForTrue="{x:Static Visibility.Visible}"/>
|
||||
</Binding.Converter>
|
||||
</Binding>
|
||||
</Path.Visibility>
|
||||
</Path>
|
||||
<ContentControl Grid.Column="1" Content="{Binding}"/>
|
||||
</Grid>
|
||||
</DataTemplate>
|
||||
|
||||
<Style x:Key="LargeMenuListBoxItemBase" TargetType="ListBoxItem">
|
||||
<Setter Property="HorizontalAlignment" Value="Stretch"/>
|
||||
<Setter Property="HorizontalContentAlignment" Value="Left"/>
|
||||
<Setter Property="VerticalAlignment" Value="Stretch"/>
|
||||
<Setter Property="VerticalContentAlignment" Value="Center"/>
|
||||
<Setter Property="BorderThickness" Value="0"/>
|
||||
<Setter Property="Background">
|
||||
<Setter.Value>
|
||||
<SolidColorBrush Color="Transparent"/>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
<Setter Property="Padding" Value="6"/>
|
||||
<Setter Property="Template" Value="{StaticResource LargeMenuListBoxItemTemplate}"/>
|
||||
</Style>
|
||||
|
||||
<Style x:Key="LargeMenuListBoxItem" TargetType="ListBoxItem" BasedOn="{StaticResource LargeMenuListBoxItemBase}">
|
||||
<Setter Property="ContentTemplate" Value="{StaticResource CheckableListBoxItemContentTemplate}"/>
|
||||
</Style>
|
||||
|
||||
<Style x:Key="LargeMenuListBox" TargetType="ListBox">
|
||||
<Setter Property="HorizontalAlignment" Value="Stretch"/>
|
||||
<Setter Property="ItemContainerStyle" Value="{StaticResource LargeMenuListBoxItem}"/>
|
||||
<Setter Property="BorderThickness" Value="0"/>
|
||||
<Setter Property="Background">
|
||||
<Setter.Value>
|
||||
<SolidColorBrush Color="Transparent"/>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
|
||||
<ControlTemplate x:Key="NakedListBoxTemplate" TargetType="ListBox">
|
||||
<ItemsPresenter/>
|
||||
</ControlTemplate>
|
||||
|
||||
<ControlTemplate x:Key="NakedListBoxItemTemplate" TargetType="ListBoxItem">
|
||||
<Border HorizontalAlignment="Stretch" VerticalAlignment="Stretch" BorderThickness="0">
|
||||
<Border.Background>
|
||||
<SolidColorBrush Color="Transparent"/>
|
||||
</Border.Background>
|
||||
<ContentPresenter/>
|
||||
</Border>
|
||||
</ControlTemplate>
|
||||
|
||||
<Style x:Key="NakedListBoxItem" TargetType="ListBoxItem">
|
||||
<Setter Property="Template" Value="{StaticResource NakedListBoxItemTemplate}"/>
|
||||
</Style>
|
||||
|
||||
<Style x:Key="NakedListBox" TargetType="ListBox">
|
||||
<Setter Property="Template" Value="{StaticResource NakedListBoxTemplate}"/>
|
||||
<Setter Property="ItemContainerStyle" Value="{StaticResource NakedListBoxItem}"/>
|
||||
</Style>
|
||||
|
||||
<Style x:Key="CommonButton" BasedOn="{StaticResource {x:Type Button}}" TargetType="{x:Type Button}">
|
||||
<Setter Property="MinWidth" Value="60" />
|
||||
<Setter Property="Margin" Value="4" />
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:uc="clr-namespace:mbts.ui"
|
||||
xmlns:u="clr-namespace:mbts.ui"
|
||||
xmlns:cv="clr-namespace:mbts.ui.converters"
|
||||
xmlns:uspcv="clr-namespace:mbts.ui.spark.converters"
|
||||
xmlns:sys="clr-namespace:System;assembly=mscorlib">
|
||||
<ResourceDictionary
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:uc="clr-namespace:mbts.ui"
|
||||
xmlns:u="clr-namespace:mbts.ui"
|
||||
xmlns:cv="clr-namespace:mbts.ui.converters"
|
||||
xmlns:uspcv="clr-namespace:mbts.ui.spark.converters"
|
||||
xmlns:ut="clr-namespace:mbts.ui.utilities"
|
||||
xmlns:sys="clr-namespace:System;assembly=mscorlib"
|
||||
>
|
||||
<cv:BooleanOrConverter x:Key="OrConverter" />
|
||||
<cv:BooleanAndConverter x:Key="AndConverter" />
|
||||
<cv:NotConverter x:Key="NotConverter" />
|
||||
@@ -25,6 +28,10 @@
|
||||
<cv:EnumDescriptionConverter x:Key="EnumDescriptionConverter" />
|
||||
<cv:EnumFlagsDescriptionConverter x:Key="EnumFlagsDescriptionConverter"/>
|
||||
<cv:EnumerableDoubleStringConverter x:Key="EnumerableDoublesConverter" />
|
||||
<cv:ValueConverterPipe x:Key="TitleCaseConverter">
|
||||
<x:Static Member="CamelCaseSpacesConverter" MemberType="{x:Type ut:DisplayNameHelper}"/>
|
||||
<x:Static Member="SnakeToTitleCaseConverter" MemberType="{x:Type ut:DisplayNameHelper}"/>
|
||||
</cv:ValueConverterPipe>
|
||||
<cv:CellPathConverter x:Key="CellPathConverter" />
|
||||
<cv:CellKeyConverter x:Key="CellKeyConverter" />
|
||||
<cv:SecurityDisplayTextConverter x:Key="SecurityTextConverter"/>
|
||||
|
||||
@@ -21,6 +21,9 @@
|
||||
<DataTemplate x:Key="EnumFlagsDescriptionTemplate">
|
||||
<TextBlock Text="{Binding Converter={StaticResource EnumFlagsDescriptionConverter}}" />
|
||||
</DataTemplate>
|
||||
<DataTemplate x:Key="TitleCaseTemplate">
|
||||
<TextBlock Text="{Binding Converter={StaticResource TitleCaseConverter}}"/>
|
||||
</DataTemplate>
|
||||
<ItemsPanelTemplate x:Key="HorizontalItemsPanelTemplate">
|
||||
<StackPanel Orientation="Horizontal"/>
|
||||
</ItemsPanelTemplate>
|
||||
|
||||
@@ -150,6 +150,8 @@
|
||||
|
||||
<Color x:Key="HeaderBackgroundBorderColor">#FFAAD1FF</Color>
|
||||
<Style x:Key="BTSDialog" TargetType="{x:Type Window}" BasedOn="{StaticResource BTSWindow}">
|
||||
<Setter Property="AllowsTransparency" Value="True"/>
|
||||
<!--<Setter Property="Background" Value="Transparent"/>-->
|
||||
<Setter Property="MinHeight" Value="100" />
|
||||
<Setter Property="MinWidth" Value="100" />
|
||||
<Setter Property="Template">
|
||||
@@ -162,6 +164,11 @@
|
||||
<RowDefinition Height="1" />
|
||||
<RowDefinition />
|
||||
</Grid.RowDefinitions>
|
||||
<Rectangle Grid.Row="0" HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
|
||||
<Rectangle.Fill>
|
||||
<SolidColorBrush Color="#01000000"/>
|
||||
</Rectangle.Fill>
|
||||
</Rectangle>
|
||||
<StackPanel HorizontalAlignment="Left" VerticalAlignment="Stretch" Orientation="Horizontal">
|
||||
<Image SnapsToDevicePixels="True" Height="14" Margin="4,2" VerticalAlignment="Center" HorizontalAlignment="Left" Stretch="Uniform" Source="{TemplateBinding Icon}" RenderOptions.BitmapScalingMode="HighQuality" />
|
||||
<TextBlock Text="{TemplateBinding Title}" VerticalAlignment="Center" HorizontalAlignment="Stretch" x:Name="_title" />
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
<RowDefinition Height="Auto"/>
|
||||
</Grid.RowDefinitions>
|
||||
<usp:GlobalAlertsView/>
|
||||
<c:DialogSkirt Grid.Row="1" Padding="{StaticResource DialogSkirtPadding}">
|
||||
<c:DialogSkirt Grid.Row="1" Padding="{StaticResource DialogSkirtPadding}" BorderThickness="0">
|
||||
<c:DialogSkirt.RightItems>
|
||||
<x:Array Type="{x:Type Button}">
|
||||
<Button Margin="{StaticResource DialogSkirtButtonMargin}" Command="{Binding apply_cmd}">
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
xmlns:cv="clr-namespace:mbts.ui.converters"
|
||||
xmlns:stg="clr-namespace:mbts.ui.settings"
|
||||
xmlns:usp="clr-namespace:mbts.ui.spark"
|
||||
xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
|
||||
xmlns:sys="clr-namespace:System;assembly=mscorlib"
|
||||
>
|
||||
<Grid>
|
||||
@@ -21,7 +22,7 @@
|
||||
<sys:Double x:Key="SecurityWidth">130</sys:Double>
|
||||
<sys:Double x:Key="DoublePredicateWidth">210</sys:Double>
|
||||
<sys:Double x:Key="SideWidth">100</sys:Double>
|
||||
<sys:Double x:Key="SoundWidth">163</sys:Double>
|
||||
<sys:Double x:Key="SoundWidth">130</sys:Double>
|
||||
<Thickness x:Key="CellPadding">10,8</Thickness>
|
||||
<!--Converters-->
|
||||
<cv:AppendTextConverter x:Key="DropDownSharedSizeGroupConverter" Suffix="_DropDownWidth"/>
|
||||
@@ -195,26 +196,21 @@
|
||||
SelectedItem="{Binding predicate.side}"
|
||||
ItemTemplate="{StaticResource EnumFlagsDescriptionTemplate}"
|
||||
/>
|
||||
<Grid c:DIYGrid.ColumnKey="Sound" Margin="{StaticResource CellPadding}">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<ComboBox
|
||||
ItemsSource="{Binding sound.system_sound_options}"
|
||||
SelectedItem="{Binding sound.system_sound}"
|
||||
ItemTemplate="{StaticResource EnumDescriptionTemplate}"
|
||||
HorizontalAlignment="Stretch"
|
||||
/>
|
||||
<Button
|
||||
Grid.Column="1"
|
||||
Margin="4,0,0,0"
|
||||
HorizontalAlignment="Left"
|
||||
Command="{x:Static c:SoundAnimationView.PreviewSoundCommand}"
|
||||
CommandParameter="{Binding sound.system_sound}"
|
||||
Style="{StaticResource RoundPlayButton}"
|
||||
/>
|
||||
</Grid>
|
||||
<ComboBox
|
||||
x:Name="sound_dropdown"
|
||||
c:DIYGrid.ColumnKey="Sound"
|
||||
Margin="{StaticResource CellPadding}"
|
||||
ItemsSource="{Binding sound.system_sound_options}"
|
||||
SelectedItem="{Binding sound.system_sound}"
|
||||
ItemTemplate="{StaticResource EnumDescriptionTemplate}"
|
||||
HorizontalAlignment="Stretch"
|
||||
>
|
||||
<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"/>
|
||||
@@ -225,14 +221,11 @@
|
||||
</DataTemplate>
|
||||
</c:DIYGrid.RowTemplate>
|
||||
</c:DIYGrid>
|
||||
<TextBlock Grid.Row="1" HorizontalAlignment="Left" Margin="4,8">
|
||||
<Hyperlink Command="{Binding add_alert_cmd}">
|
||||
Click to Add Alert
|
||||
</Hyperlink>
|
||||
</TextBlock>
|
||||
<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>
|
||||
</DataTemplate>
|
||||
<!--<DataTemplate DataType-->
|
||||
</ResourceDictionary>
|
||||
</Grid.Resources>
|
||||
<Grid.RowDefinitions>
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
Title="{Binding title, StringFormat='{}{0} Settings'}"
|
||||
Icon="{x:Static u:Icons.Admin}"
|
||||
Style="{StaticResource BTSDialog}"
|
||||
UseLayoutRounding="True"
|
||||
>
|
||||
<Window.Resources>
|
||||
<!--Primitive Constants-->
|
||||
@@ -34,6 +35,30 @@
|
||||
<cv:KeyValue Key="{u:True}" Value="{u:FontWeight Bold}"/>
|
||||
</cv:ValueMap>
|
||||
<!--Styles (Shared/Common)-->
|
||||
<DataTemplate x:Key="FontPickerTemplate">
|
||||
<c:FontPicker SelectedFontFamily="{Binding font_family}" SelectedFontSize="{Binding font_size}" SelectedFontWeight="{Binding font_weight}"/>
|
||||
</DataTemplate>
|
||||
<DataTemplate x:Key="FontDisplayTemplate">
|
||||
<TextBlock FontFamily="{Binding font_family}" FontSize="{Binding font_size}" FontWeight="{Binding font_weight}">
|
||||
<TextBlock.Text>
|
||||
<MultiBinding StringFormat="{}{0}, {1:#0.##} pt">
|
||||
<Binding Path="font_family"/>
|
||||
<Binding Path="font_size"/>
|
||||
</MultiBinding>
|
||||
</TextBlock.Text>
|
||||
</TextBlock>
|
||||
</DataTemplate>
|
||||
<DataTemplate x:Key="FontFamilyAndWeightDisplayTemplate">
|
||||
<TextBlock FontFamily="{Binding font_family}" FontSize="{Binding font_size}" FontWeight="{Binding font_weight}">
|
||||
<TextBlock.Text>
|
||||
<MultiBinding StringFormat="{}{0}, {1}, {2:#0.##} pt">
|
||||
<Binding Path="font_family"/>
|
||||
<Binding Path="font_weight"/>
|
||||
<Binding Path="font_size"/>
|
||||
</MultiBinding>
|
||||
</TextBlock.Text>
|
||||
</TextBlock>
|
||||
</DataTemplate>
|
||||
<Style x:Key="FontPickerPopupControl" TargetType="c:PopupControl">
|
||||
<Setter Property="Background" Value="{StaticResource ComboBox.Static.Background}"/>
|
||||
<Setter Property="HorizontalAlignment" Value="Stretch"/>
|
||||
@@ -41,27 +66,8 @@
|
||||
<Setter Property="MinHeight" Value="24"/>
|
||||
<Setter Property="Header" Value="{Binding}"/>
|
||||
<Setter Property="Content" Value="{Binding}"/>
|
||||
<Setter Property="HeaderTemplate">
|
||||
<Setter.Value>
|
||||
<DataTemplate>
|
||||
<TextBlock FontFamily="{Binding font_family}" FontSize="{Binding font_size}" FontWeight="{Binding font_weight}">
|
||||
<TextBlock.Text>
|
||||
<MultiBinding StringFormat="{}{0}, {1:#0.##} pt">
|
||||
<Binding Path="font_family"/>
|
||||
<Binding Path="font_size"/>
|
||||
</MultiBinding>
|
||||
</TextBlock.Text>
|
||||
</TextBlock>
|
||||
</DataTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
<Setter Property="ContentTemplate">
|
||||
<Setter.Value>
|
||||
<DataTemplate>
|
||||
<c:FontPicker SelectedFontFamily="{Binding font_family}" SelectedFontSize="{Binding font_size}" SelectedFontWeight="{Binding font_weight}"/>
|
||||
</DataTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
<Setter Property="ContentTemplate" Value="{StaticResource FontPickerTemplate}"/>
|
||||
<Setter Property="HeaderTemplate" Value="{StaticResource FontDisplayTemplate}"/>
|
||||
</Style>
|
||||
<!--DataTemplates-->
|
||||
<DataTemplate x:Key="GridSectionCoreContentTemplate">
|
||||
@@ -240,6 +246,7 @@
|
||||
</Style>
|
||||
</u:StyleSelectorKeyValue>
|
||||
</u:KeyedStyleSelector>
|
||||
|
||||
<DataTemplate x:Key="AnchorItemContentTemplate">
|
||||
<Border Padding="4">
|
||||
<Grid>
|
||||
@@ -332,19 +339,11 @@
|
||||
<u:KeyedTemplateSelector x:Key="GridSettingsTemplateSelector">
|
||||
<u:TemplateSelectorKeyValue Key="{x:Static usp:GridSettingsTemplateKeys.SimpleGrid}">
|
||||
<DataTemplate>
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="*"/>
|
||||
</Grid.RowDefinitions>
|
||||
<c:ActionMenu HorizontalAlignment="Right" Margin="0,2,0,0" DataContext="{Binding DataContext, RelativeSource={RelativeSource AncestorType=usp:ShellSettingsDialog}}" ItemsSource="{Binding action_menu_items}"/>
|
||||
<usp:GridSettingsView
|
||||
Grid.Row="1"
|
||||
GridSectionContent="{Binding}"
|
||||
GridSectionContentTemplate="{StaticResource GridSectionCoreContentTemplate}"
|
||||
CellContentTemplate="{DynamicResource DefaultCellContentTemplate}"
|
||||
/>
|
||||
</Grid>
|
||||
<usp:GridSettingsView
|
||||
GridSectionContent="{Binding}"
|
||||
GridSectionContentTemplate="{StaticResource GridSectionCoreContentTemplate}"
|
||||
CellContentTemplate="{DynamicResource DefaultCellContentTemplate}"
|
||||
/>
|
||||
</DataTemplate>
|
||||
</u:TemplateSelectorKeyValue>
|
||||
<u:TemplateSelectorKeyValue Key="{x:Static usp:GridSettingsTemplateKeys.Ladder}">
|
||||
@@ -374,6 +373,7 @@
|
||||
<ColumnDefinition Width="*"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<Border Grid.ColumnSpan="4" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Background="{StaticResource HeaderBackgroundBrush}" BorderBrush="{StaticResource HeaderBackgroundBorderBrush}" BorderThickness="1,0,1,1"/>
|
||||
<TextBlock Grid.Column="1" VerticalAlignment="Center">Profile:</TextBlock>
|
||||
<ComboBox Grid.Column="2" Margin="8,4" ItemsSource="{Binding Source={StaticResource ProfilesViewSource}}" SelectedItem="{Binding selected_profile}" ItemTemplateSelector="{StaticResource ProfileItemTemplateSelector}">
|
||||
<ComboBox.GroupStyle>
|
||||
@@ -547,52 +547,25 @@
|
||||
</MenuItem.ToolTip>
|
||||
</MenuItem>
|
||||
<Separator VerticalAlignment="Stretch" Width="1" Margin="2,2,1,2" Background="Gray"/>
|
||||
<c:ActionMenu Grid.Column="4" HorizontalAlignment="Right" Margin="0,0,4,0" VerticalAlignment="Center" ItemsSource="{Binding action_menu_items}"/>
|
||||
<c:ActionMenu Grid.Column="4" HorizontalAlignment="Right" Margin="0,0,4,0" ItemsSource="{Binding action_menu_items}" Style="{StaticResource SparkToolBarItem}"/>
|
||||
</ToolBar>
|
||||
<usp:LadderSettingsView Grid.Row="1" Grid.Column="1" Grid.ColumnSpan="3" HorizontalAlignment="Left" Width="758" MinHeight="234" Margin="0,10,8,0" DataContext="{Binding selected_profile.value}"/>
|
||||
</Grid>
|
||||
</DataTemplate>
|
||||
</u:TemplateSelectorKeyValue>
|
||||
<u:TemplateSelectorKeyValue Key="{x:Static usp:GridSettingsTemplateKeys.OrderBook}">
|
||||
<DataTemplate>
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="*"/>
|
||||
</Grid.RowDefinitions>
|
||||
<c:ActionMenu HorizontalAlignment="Right" Margin="0,2,0,0" DataContext="{Binding DataContext, RelativeSource={RelativeSource AncestorType=usp:ShellSettingsDialog}}" ItemsSource="{Binding action_menu_items}"/>
|
||||
<usp:GridSettingsView
|
||||
Grid.Row="1"
|
||||
DataContext="{Binding core_settings_vm}"
|
||||
GridSectionContent="{Binding DataContext.instance_vm, RelativeSource={RelativeSource AncestorType=usp:ShellSettingsDialog}}"
|
||||
CellContentTemplate="{DynamicResource DefaultCellContentTemplate}"
|
||||
>
|
||||
<usp:GridSettingsView.GridSectionContentTemplate>
|
||||
<DataTemplate>
|
||||
<StackPanel Grid.IsSharedSizeScope="True">
|
||||
<ContentControl Content="{Binding core_settings_vm}" ContentTemplate="{StaticResource GridSectionCoreContentTemplate}"/>
|
||||
<CheckBox Content="Show Full Algo Tree">
|
||||
<CheckBox.Margin>
|
||||
<Thickness Top="{StaticResource VerticalPush1}"/>
|
||||
</CheckBox.Margin>
|
||||
<CheckBox.IsChecked>
|
||||
<Binding Path="(orderbook:OrderBookSettingsVM.algo_tree_display_mode)">
|
||||
<Binding.Converter>
|
||||
<cv:ReverseConverter>
|
||||
<cv:BooleanToObjectConverter ValueForFalse="{x:Static orderbook:AlgoTreeDisplayMode.RootAndLeafNodes}" ValueForTrue="{x:Static orderbook:AlgoTreeDisplayMode.FullTree}"/>
|
||||
</cv:ReverseConverter>
|
||||
</Binding.Converter>
|
||||
</Binding>
|
||||
</CheckBox.IsChecked>
|
||||
</CheckBox>
|
||||
</StackPanel>
|
||||
</DataTemplate>
|
||||
</usp:GridSettingsView.GridSectionContentTemplate>
|
||||
</usp:GridSettingsView>
|
||||
</Grid>
|
||||
</DataTemplate>
|
||||
<DataTemplate DataType="{x:Type orderbook:OrderBookSettingsVM}">
|
||||
<ScrollViewer HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Auto">
|
||||
<usp:OrderBookSettingsView/>
|
||||
</ScrollViewer>
|
||||
</DataTemplate>
|
||||
</u:TemplateSelectorKeyValue>
|
||||
<u:TemplateSelectorKeyValue Key="{x:Static usp:GridSettingsTemplateKeys.PNL}">
|
||||
<DataTemplate>
|
||||
<usp:PNLSettingsView/>
|
||||
</DataTemplate>
|
||||
</u:TemplateSelectorKeyValue>
|
||||
<u:TemplateSelectorKeyValue Key="BahBahBlackSheep">
|
||||
<DataTemplate>
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
@@ -770,17 +743,16 @@
|
||||
LeftItems="{Binding left_skirt_items}"
|
||||
LeftItemContainerStyleSelector="{StaticResource LeftButtonStyleSelector}"
|
||||
RightItems="{Binding right_skirt_items}"
|
||||
BorderBrush="DarkGray"
|
||||
BorderThickness="0,1,0,0"
|
||||
BorderThickness="0"
|
||||
>
|
||||
<c:DialogSkirt.Background>
|
||||
<!--<c:DialogSkirt.Background>
|
||||
<LinearGradientBrush StartPoint="0,0" EndPoint="1,0">
|
||||
<LinearGradientBrush.GradientStops>
|
||||
<GradientStop Color="Transparent"/>
|
||||
<GradientStop Color="{x:Static u:BTSColors.dialog_skirt}" Offset="1"/>
|
||||
</LinearGradientBrush.GradientStops>
|
||||
</LinearGradientBrush>
|
||||
</c:DialogSkirt.Background>
|
||||
</c:DialogSkirt.Background>-->
|
||||
</c:DialogSkirt>
|
||||
</Grid>
|
||||
</Window>
|
||||
@@ -125,6 +125,7 @@ namespace mbts.ui.spark {
|
||||
static class GridSettingsTemplateKeys {
|
||||
public const string
|
||||
SimpleGrid = nameof(SimpleGrid),
|
||||
Fills = nameof(Fills),
|
||||
Ladder = nameof(Ladder),
|
||||
OrderBook = nameof(OrderBook),
|
||||
PNL = nameof(PNL);
|
||||
|
||||
@@ -33,9 +33,7 @@ namespace mbts.spark {
|
||||
public ShellSettingsDialogVM(IWindowSettingsManager<T> settings_manager, Func<UserSettings, T> get_factory_settings, string settings_file_ext, Func<T, TVM> vm_factory, Func<T, T> merge_with_current = null, bool show_save_as_defaults = true) {
|
||||
if (merge_with_current == null)
|
||||
merge_with_current = s => s;
|
||||
// by default, each new window will use their own instance settings
|
||||
_use_default = backing(nameof(use_default), settings_manager.instance_settings == null);
|
||||
default_vm = vm_factory(settings_manager.default_settings);
|
||||
// instance vm is what will always be visible on the screen
|
||||
instance_vm = vm_factory(settings_manager.current_settings());
|
||||
// items
|
||||
@@ -70,8 +68,6 @@ namespace mbts.spark {
|
||||
// maybe dispose vms
|
||||
if (instance_vm is IDisposable dispose_ivm)
|
||||
_resources.Add(dispose_ivm);
|
||||
if (default_vm is IDisposable dispose_dvm)
|
||||
_resources.Add(dispose_dvm);
|
||||
// local methods
|
||||
void apply() {
|
||||
var result = merge_with_current(instance_vm.get_result());
|
||||
@@ -99,7 +95,6 @@ namespace mbts.spark {
|
||||
|
||||
public string title { get => _title; set => Set(ref _title, value); }
|
||||
public TVM instance_vm { get; }
|
||||
public TVM default_vm { get; }
|
||||
public IReadOnlyCollection<IMenuItemVM> action_menu_items { get; }
|
||||
public IEnumerable<DialogSkirtItem> left_skirt_items => _left_skirt_items.Value;
|
||||
public IEnumerable<DialogSkirtItem> right_skirt_items {
|
||||
@@ -438,4 +433,4 @@ namespace mbts.spark {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -24,6 +24,9 @@ namespace mbts.ui.utilities {
|
||||
SnakeToTitleCaseConverter = ValueConverter.create(obj => snake_to_title_case(obj?.ToString())),
|
||||
ExpressionSnakeToTitleCaseConverter = ValueConverter.create(obj => snake_to_title_case(obj?.ToString(), @"[A-Za-z0-9_\-\s\(\)<>=]*"));
|
||||
|
||||
public static string expression_snake_to_title_case(string expression, TextCapitalizationFormat capitalization_format = TextCapitalizationFormat.Default) =>
|
||||
snake_to_title_case(expression, @"[A-Za-z0-9_\-\s\(\)<>=]*");
|
||||
|
||||
// Adds spaces to CamelCase => "Camel Case"
|
||||
public static string camel_to_title_case(string name, TextCapitalizationFormat capitalization_format = TextCapitalizationFormat.Default) {
|
||||
if (name != null) {
|
||||
|
||||
@@ -31,9 +31,9 @@ namespace mbts.ui.utility.text {
|
||||
create(font_family, TextUtils.from_open_type_weight_safe(font_weight));
|
||||
|
||||
public static GlyphContext create(string font_family, FontWeight font_weight) =>
|
||||
create(new System.Windows.Media.FontFamily(font_family), font_weight);
|
||||
create(new FontFamily(font_family), font_weight);
|
||||
|
||||
public static GlyphContext create(System.Windows.Media.FontFamily font_family, FontWeight font_weight) {
|
||||
public static GlyphContext create(FontFamily font_family, FontWeight font_weight) {
|
||||
var typeface = new Typeface(font_family, FontStyles.Normal, font_weight, FontStretches.Normal);
|
||||
return new GlyphContext(typeface.TryGetGlyphTypeface(out GlyphTypeface gt) ? gt : _default_glyph_typeface);
|
||||
}
|
||||
@@ -45,10 +45,10 @@ namespace mbts.ui.utility.text {
|
||||
string family = _glyph_typeface.FamilyNames.FirstOrDefault().Value;
|
||||
if (string.IsNullOrEmpty(family)) // idk how this would happen, but just to be safe
|
||||
family = TextUtils.StandardFontFamilyName;
|
||||
return create(new System.Windows.Media.FontFamily(family), font_weight);
|
||||
return create(new FontFamily(family), font_weight);
|
||||
}
|
||||
|
||||
public GlyphContext with(System.Windows.Media.FontFamily font_family) =>
|
||||
public GlyphContext with(FontFamily font_family) =>
|
||||
create(font_family, _glyph_typeface.Weight);
|
||||
|
||||
public GlyphRun get_glyph_run(
|
||||
@@ -72,8 +72,16 @@ namespace mbts.ui.utility.text {
|
||||
get_glyph_run(text, font_size, 0, 0, available_width, available_height, x_alignment, y_alignment, padding);
|
||||
|
||||
public GlyphRun get_glyph_run(
|
||||
string text, double font_size, double left, double top, double right, double bottom,
|
||||
HorizontalAlignment x_alignment, VerticalAlignment y_alignment, Thickness padding) {
|
||||
string text,
|
||||
double font_size,
|
||||
double left,
|
||||
double top,
|
||||
double right,
|
||||
double bottom,
|
||||
HorizontalAlignment x_alignment,
|
||||
VerticalAlignment y_alignment,
|
||||
Thickness padding,
|
||||
bool use_layout_rounding = true) {
|
||||
//
|
||||
if (string.IsNullOrEmpty(text))
|
||||
return null;
|
||||
@@ -82,6 +90,8 @@ namespace mbts.ui.utility.text {
|
||||
if (!_try_get_glyph_params(text, font_size, ref glyph_indices, ref advance_widths, out double glyph_width))
|
||||
return null;
|
||||
var origin = _calculate_glyph_run_origin(font_size, left, top, right, bottom, glyph_width, x_alignment, y_alignment, padding);
|
||||
if (use_layout_rounding)
|
||||
origin = origin.RoundToLayout();
|
||||
return new GlyphRun(_glyph_typeface, 0, false, font_size, HighDpiExtensions.fdpi_ratio, glyph_indices, origin, advance_widths, null, null, null, null, null, null);
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
using System.Collections;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
|
||||
namespace mbts.ui {
|
||||
// This class is a FrameworkElement so it can be generated by a DataTemplate.
|
||||
// Such a DataTemplate may be referenced by multiple grids,
|
||||
// which is useful because single instances of row/column definitions cannot be shared.
|
||||
class GridArchitectureProxy : FrameworkElement {
|
||||
public static readonly DependencyProperty TemplateProperty =
|
||||
DependencyProperty.RegisterAttached("Template", typeof(DataTemplate), typeof(GridArchitectureProxy), new PropertyMetadata(OnTemplateChanged));
|
||||
|
||||
static void OnTemplateChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) {
|
||||
if (e.NewValue is DataTemplate dt) {
|
||||
var target = (Grid)d;
|
||||
var content = dt.LoadContent();
|
||||
if (content is GridArchitectureProxy gtp) {
|
||||
var row_defs = gtp.RowDefinitions;
|
||||
if (row_defs != null) {
|
||||
target.RowDefinitions.Clear();
|
||||
target.RowDefinitions.AddRange(row_defs);
|
||||
}
|
||||
var col_defs = gtp.ColumnDefinitions;
|
||||
if (col_defs != null) {
|
||||
target.ColumnDefinitions.Clear();
|
||||
target.ColumnDefinitions.AddRange(col_defs);
|
||||
}
|
||||
}
|
||||
} else if (e.NewValue is GridArchitectureProxy gp) {
|
||||
var target = (Grid)d;
|
||||
// Note that this does not react to the rows/columns changing,
|
||||
// the expectation was that these would be assigned once in practice,
|
||||
// so implementing such 'row/columndefs changed' events/callbacks appeared unneccesary.
|
||||
// Anyone can implement it later, if the need arises.
|
||||
var row_defs = gp.RowDefinitions;
|
||||
if (row_defs != null) {
|
||||
target.RowDefinitions.Clear();
|
||||
target.RowDefinitions.AddRange(row_defs);
|
||||
}
|
||||
var col_defs = gp.ColumnDefinitions;
|
||||
if (col_defs != null) {
|
||||
target.ColumnDefinitions.Clear();
|
||||
target.ColumnDefinitions.AddRange(col_defs);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static DataTemplate GetTemplate(DependencyObject d) => d.GetValue(TemplateProperty) as DataTemplate;
|
||||
public static void SetTemplate(DependencyObject d, object value) => d.SetValue(TemplateProperty, value);
|
||||
|
||||
public static readonly DependencyProperty RowDefinitionsProperty =
|
||||
WPFHelper.create_dp<IEnumerable, GridArchitectureProxy>(nameof(RowDefinitions));
|
||||
|
||||
public IEnumerable RowDefinitions {
|
||||
get => GetValue(RowDefinitionsProperty) as IEnumerable;
|
||||
set => SetValue(RowDefinitionsProperty, value);
|
||||
}
|
||||
|
||||
public static readonly DependencyProperty ColumnDefinitionsProperty =
|
||||
WPFHelper.create_dp<IEnumerable, GridArchitectureProxy>(nameof(ColumnDefinitions));
|
||||
|
||||
public IEnumerable ColumnDefinitions {
|
||||
get => GetValue(ColumnDefinitionsProperty) as IEnumerable;
|
||||
set => SetValue(ColumnDefinitionsProperty, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,234 @@
|
||||
using mbts.ui.shell;
|
||||
using mbts.utils;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace bts.utils {
|
||||
readonly struct Named<T> : IEquatable<Named<T>>, IComparable<Named<T>>, IComparable {
|
||||
public readonly string name;
|
||||
public readonly T value;
|
||||
|
||||
public Named(string name, T value) {
|
||||
this.name = name;
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
public int CompareTo(Named<T> other) {
|
||||
int comp = string.Compare(name, other.name);
|
||||
if (comp == 0)
|
||||
comp = Comparer<T>.Default.Compare(value, other.value);
|
||||
return comp;
|
||||
}
|
||||
int IComparable.CompareTo(object obj) =>
|
||||
obj is Named<T> other ? CompareTo(other) : throw ExceptionBuilder.Argument.not_of_type<Named<T>>(obj, nameof(obj));
|
||||
|
||||
public bool Equals(Named<T> other) =>
|
||||
name == other.name &&
|
||||
Equals(value, other.value);
|
||||
|
||||
public override bool Equals(object obj) =>
|
||||
obj is Named<T> other && Equals(other);
|
||||
|
||||
public override int GetHashCode() =>
|
||||
EqualityComparer.auto_phase(
|
||||
name?.GetHashCode() ?? 0,
|
||||
value?.GetHashCode() ?? 0);
|
||||
|
||||
public override string ToString() =>
|
||||
name;
|
||||
}
|
||||
|
||||
class NamedVM : ViewModelBase, IEquatable<NamedVM> {
|
||||
string _name;
|
||||
|
||||
public NamedVM() { }
|
||||
|
||||
public NamedVM(string name) =>
|
||||
this.name = name;
|
||||
|
||||
public NamedVM(string name, bool is_read_only) {
|
||||
this.name = name;
|
||||
this.is_read_only = is_read_only;
|
||||
}
|
||||
|
||||
public bool is_read_only { get; }
|
||||
|
||||
public string name {
|
||||
get => _name;
|
||||
set {
|
||||
_assert_can_write();
|
||||
Set(ref _name, value);
|
||||
}
|
||||
}
|
||||
|
||||
public bool Equals(NamedVM other) =>
|
||||
other != null &&
|
||||
name == other.name;
|
||||
|
||||
public override bool Equals(object obj) =>
|
||||
is_read_only ?
|
||||
Equals(obj as NamedVM) :
|
||||
base.Equals(obj);
|
||||
|
||||
public override int GetHashCode() =>
|
||||
is_read_only ?
|
||||
_name == null ? 0 : _name.GetHashCode() :
|
||||
base.GetHashCode();
|
||||
|
||||
void _assert_can_write() {
|
||||
if (is_read_only)
|
||||
throw new InvalidOperationException("NamedVM is read only.");
|
||||
}
|
||||
}
|
||||
|
||||
class NamedVM<T> : ViewModelBase, IEquatable<NamedVM<T>> {
|
||||
string _name;
|
||||
T _value;
|
||||
|
||||
public NamedVM() { }
|
||||
|
||||
public NamedVM(string name) =>
|
||||
_name = name;
|
||||
|
||||
public NamedVM(string name, T value)
|
||||
: this(name) => _value = value;
|
||||
|
||||
public NamedVM(string name, T value, bool is_name_read_only, bool is_value_read_only)
|
||||
: this(name, value) {
|
||||
this.is_name_read_only = is_name_read_only;
|
||||
this.is_value_read_only = is_value_read_only;
|
||||
}
|
||||
|
||||
public bool is_name_read_only { get; }
|
||||
public bool is_value_read_only { get; }
|
||||
|
||||
public string name {
|
||||
get => _name;
|
||||
set {
|
||||
if (is_name_read_only)
|
||||
throw ExceptionBuilder.InvalidOperation.is_read_only();
|
||||
Set(ref _name, value);
|
||||
}
|
||||
}
|
||||
|
||||
public T value {
|
||||
get => _value;
|
||||
set {
|
||||
if (is_value_read_only)
|
||||
throw ExceptionBuilder.InvalidOperation.is_read_only();
|
||||
Set(ref _value, value);
|
||||
}
|
||||
}
|
||||
|
||||
public bool Equals(NamedVM<T> other) =>
|
||||
other != null &&
|
||||
name == other.name &&
|
||||
EqualityComparer<T>.Default.Equals(value, other.value);
|
||||
|
||||
public override bool Equals(object obj) {
|
||||
if (obj == this)
|
||||
return true;
|
||||
/// 'this' and 'obj' must be entirely non-mutable (<see cref="is_name_read_only"/>, <see cref="is_value_read_only"/>)
|
||||
/// to be considered equivalent, in addition to having equal <see cref="value"/> and <see cref="name"/> members.
|
||||
if (!(obj is NamedVM<T> other))
|
||||
return false;
|
||||
return is_value_read_only && is_name_read_only &&
|
||||
other.is_name_read_only && other.is_value_read_only &&
|
||||
name == other.name && EqualityComparer<T>.Default.Equals(value, other.value);
|
||||
}
|
||||
|
||||
public override int GetHashCode() {
|
||||
// There are 2 main components to hash: name and value.
|
||||
// Each shall use 16 bits of the 32 available to create the hash code.
|
||||
// Both components also have a corresponding 'is_read_only' property,
|
||||
// which shall be represented as a flag in the first bit of the 16 appropriated to each component.
|
||||
// If a component is not read only, it must not be included in the hash code (technically, it will be represented as a constant: 0).
|
||||
int hash = 0;
|
||||
if (is_name_read_only) {
|
||||
hash |= 1;
|
||||
if (name != null)
|
||||
hash |= ((name.GetHashCode() % (1 << 14)) << 1);
|
||||
}
|
||||
if (is_value_read_only) {
|
||||
hash |= (1 << 16);
|
||||
if (value != null)
|
||||
hash |= ((value.GetHashCode() % (1 << 14)) << 17);
|
||||
}
|
||||
return hash;
|
||||
}
|
||||
}
|
||||
|
||||
readonly struct Overridable<T> : IEquatable<Overridable<T>> {
|
||||
public readonly T value, default_value;
|
||||
public readonly bool is_overridden, can_override;
|
||||
|
||||
public Overridable(T value, T default_value, bool can_override, bool is_overridden) {
|
||||
this.value = value;
|
||||
this.default_value = default_value;
|
||||
this.can_override = can_override;
|
||||
this.is_overridden = is_overridden;
|
||||
}
|
||||
|
||||
public T effective_value => is_overridden ? value : default_value;
|
||||
public bool effective_overridden => can_override && is_overridden;
|
||||
|
||||
public Overridable<T> with(bool? can_override = null, bool? is_overridden = null) =>
|
||||
new Overridable<T>(
|
||||
value,
|
||||
default_value,
|
||||
can_override ?? this.can_override,
|
||||
is_overridden ?? this.is_overridden);
|
||||
|
||||
public Overridable<T> with_default(T default_value) =>
|
||||
new Overridable<T>(
|
||||
value,
|
||||
default_value,
|
||||
can_override,
|
||||
is_overridden);
|
||||
|
||||
public Overridable<T> with_override(T override_value) =>
|
||||
new Overridable<T>(
|
||||
override_value,
|
||||
default_value,
|
||||
can_override,
|
||||
is_overridden);
|
||||
|
||||
public bool Equals(Overridable<T> other) =>
|
||||
is_overridden == other.is_overridden &&
|
||||
can_override == other.can_override &&
|
||||
Equals(value, other.value) &&
|
||||
Equals(default_value, other.default_value);
|
||||
|
||||
public override bool Equals(object obj) =>
|
||||
obj is Overridable<T> other && Equals(other);
|
||||
|
||||
public override int GetHashCode() {
|
||||
int hash = (value.GetHashCode() % (1 << 16)) |
|
||||
((default_value.GetHashCode() % (1 << 16)) << 16);
|
||||
if (is_overridden)
|
||||
hash |= 1 << 31;
|
||||
return hash;
|
||||
}
|
||||
}
|
||||
|
||||
class OverridableComparer<T> : IEqualityComparer<Overridable<T>> {
|
||||
readonly IEqualityComparer<T> _value_comparer;
|
||||
|
||||
public OverridableComparer(IEqualityComparer<T> value_comparer) =>
|
||||
_value_comparer = value_comparer;
|
||||
|
||||
public bool Equals(Overridable<T> x, Overridable<T> y) =>
|
||||
x.is_overridden == y.is_overridden &&
|
||||
x.can_override == y.can_override &&
|
||||
_value_comparer.Equals(x.value, y.value) &&
|
||||
_value_comparer.Equals(x.default_value, y.default_value);
|
||||
|
||||
public int GetHashCode(Overridable<T> obj) {
|
||||
int hash = (_value_comparer.GetHashCode(obj.value) % (1 << 16)) |
|
||||
((_value_comparer.GetHashCode(obj.default_value) % (1 << 16)) << 16);
|
||||
if (obj.is_overridden)
|
||||
hash |= 1 << 31;
|
||||
return hash;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4,6 +4,7 @@ using mbts.ui.extensions;
|
||||
using mbts.ui.shell;
|
||||
using mbts.ui.utility;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Collections.Specialized;
|
||||
using System.Reactive.Disposables;
|
||||
@@ -44,11 +45,20 @@ namespace mbts.ui {
|
||||
StateProperty.OverrideMetadata(typeof(MouseStateDefinition), StateProperty.DefaultMetadata.with(coerce_state));
|
||||
object coerce_state(DependencyObject d, object baseValue) {
|
||||
var msd = (MouseStateDefinition)d;
|
||||
var src = msd.MouseSource;
|
||||
return new StringBuilderEx(
|
||||
msd.Mode?.ToString() ?? string.Empty,
|
||||
msd.MouseSource?.IsMouseOver ?? false ?
|
||||
(Mouse.LeftButton == MouseButtonState.Pressed ? msd.PressedState : msd.HoverState) :
|
||||
msd.NormalState).ToString();
|
||||
src != null && src.IsMouseOver ?
|
||||
(OneOf.infer<bool, ButtonBase, MenuItem, FrameworkElement>(src, is_button_pressed, is_menu_item_pressed, is_element_pressed) ?
|
||||
msd.PressedState :
|
||||
msd.HoverState) :
|
||||
msd.NormalState).ToString();
|
||||
// is pressed impls
|
||||
bool is_button_pressed(ButtonBase bb) => bb.IsPressed;
|
||||
bool is_menu_item_pressed(MenuItem mi) => mi.IsPressed;
|
||||
bool is_element_pressed(FrameworkElement el) =>
|
||||
(Mouse.LeftButton == MouseButtonState.Pressed || Mouse.RightButton == MouseButtonState.Pressed) &&
|
||||
(el.IsMouseOver || el.IsMouseCaptured);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -85,16 +95,22 @@ namespace mbts.ui {
|
||||
Disposable.dispose(ref _mouse_hook);
|
||||
this.set_current_if_changed(StateProperty, NormalState);
|
||||
} else {
|
||||
Disposable.replace(ref _mouse_hook,
|
||||
Disposable.Create(
|
||||
mouse_source.SubscribeMouseEnter(on_mouse_event),
|
||||
mouse_source.SubscribeMouseMove(on_mouse_event),
|
||||
mouse_source.SubscribeMouseLeave(on_mouse_event),
|
||||
mouse_source.SubscribePreviewMouseDown(on_mouse_button_event),
|
||||
mouse_source.SubscribePreviewMouseUp(on_mouse_button_event)));
|
||||
Disposable.dispose(ref _mouse_hook);
|
||||
_mouse_hook = Disposable.Create(
|
||||
mouse_source.SubscribeMouseEnter(on_mouse_event),
|
||||
mouse_source.SubscribeMouseMove(on_mouse_event),
|
||||
mouse_source.SubscribeMouseLeave(on_mouse_event),
|
||||
mouse_source.SubscribePreviewMouseDown(on_mouse_event),
|
||||
mouse_source.SubscribePreviewMouseUp(on_mouse_event));
|
||||
if (mouse_source is ButtonBase bb) {
|
||||
bb.Click += on_mouse_event;
|
||||
_mouse_hook = _mouse_hook.Append(() => bb.Click -= on_mouse_event);
|
||||
} else if (mouse_source is MenuItem mi) {
|
||||
mi.Click += on_mouse_event;
|
||||
_mouse_hook = _mouse_hook.Append(() => mi.Click -= on_mouse_event);
|
||||
}
|
||||
CoerceValue(StateProperty);
|
||||
void on_mouse_event(object sender, MouseEventArgs e) => CoerceValue(StateProperty);
|
||||
void on_mouse_button_event(object sender, MouseButtonEventArgs e) => CoerceValue(StateProperty);
|
||||
void on_mouse_event(object sender, RoutedEventArgs e) => CoerceValue(StateProperty);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -217,7 +233,7 @@ namespace mbts.ui {
|
||||
}
|
||||
|
||||
[ContentProperty(nameof(StateDefinitions))]
|
||||
class StateGroupsManager : DependencyObject {
|
||||
class StateGroupsManager : FrameworkElement {
|
||||
IDisposable _state_definitions_hook;
|
||||
|
||||
public bool OverrideTarget { get; set; }
|
||||
@@ -290,6 +306,36 @@ namespace mbts.ui {
|
||||
// </VisualStateGroup>
|
||||
//</VisualStateManager.VisualStateGroups>
|
||||
class StateManager : DependencyObject {
|
||||
// This is workaround to name VisualStates/Groups in a ResourceDictionary.xaml file.
|
||||
public static readonly DependencyProperty NameProperty =
|
||||
DependencyProperty.RegisterAttached("Name", typeof(string), typeof(StateManager),
|
||||
new PropertyMetadata(OnNameChanged));
|
||||
|
||||
static void OnNameChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) {
|
||||
if (d is VisualState state)
|
||||
state.Name = e.NewValue?.ToString();
|
||||
else if (d is VisualStateGroup group)
|
||||
group.Name = e.NewValue?.ToString();
|
||||
}
|
||||
|
||||
public static string GetName(DependencyObject d) => d.GetValue(NameProperty) as string;
|
||||
public static void SetName(DependencyObject d, string value) => d.SetValue(NameProperty, value);
|
||||
|
||||
public static readonly DependencyProperty VisualStateGroupsProxyProperty =
|
||||
DependencyProperty.RegisterAttached("VisualStateGroupsProxy", typeof(IEnumerable<VisualStateGroup>), typeof(StateManager), new PropertyMetadata(OnVisualStateGroupsProxyChanged));
|
||||
|
||||
static void OnVisualStateGroupsProxyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) {
|
||||
var el = (FrameworkElement)d;
|
||||
var groups = VisualStateManager.GetVisualStateGroups(el);
|
||||
groups.Clear();
|
||||
if (e.NewValue is IEnumerable<VisualStateGroup> new_state_groups) {
|
||||
groups.AddRange(new_state_groups);
|
||||
}
|
||||
}
|
||||
|
||||
public static IEnumerable<VisualStateGroup> GetVisualStateGroupsProxy(DependencyObject d) => d.GetValue(VisualStateGroupsProxyProperty) as IEnumerable<VisualStateGroup>;
|
||||
public static void SetVisualStateGroupsProxy(DependencyObject d, IEnumerable<VisualStateGroup> value) => d.SetValue(VisualStateGroupsProxyProperty, value);
|
||||
|
||||
#region Groups
|
||||
public static readonly DependencyProperty GroupsProperty =
|
||||
DependencyProperty.RegisterAttached("Groups", typeof(StateGroupsManager), typeof(StateManager), new PropertyMetadata((d, e) => {
|
||||
@@ -301,6 +347,23 @@ namespace mbts.ui {
|
||||
public static void SetGroups(DependencyObject d, StateGroupsManager value) => d.SetValue(GroupsProperty, value);
|
||||
#endregion
|
||||
|
||||
#region GroupsTemplate
|
||||
public static readonly DependencyProperty GroupsTemplateProperty =
|
||||
DependencyProperty.RegisterAttached("GroupsTemplate", typeof(DataTemplate), typeof(StateManager), new PropertyMetadata(OnGroupsTemplateChanged));
|
||||
|
||||
static void OnGroupsTemplateChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) {
|
||||
if (e.NewValue is DataTemplate dt) {
|
||||
var target = (FrameworkElement)d;
|
||||
var content = dt.LoadContent();
|
||||
if (content is StateGroupsManager sgm)
|
||||
target.SetCurrentValue(GroupsProperty, sgm);
|
||||
}
|
||||
}
|
||||
|
||||
public static DataTemplate GetGroupsTemplate(DependencyObject d) => d.GetValue(GroupsTemplateProperty) as DataTemplate;
|
||||
public static void SetGroupsTemplate(DependencyObject d, object value) => d.SetValue(GroupsTemplateProperty, value);
|
||||
#endregion
|
||||
|
||||
public static string GetVisualState(DependencyObject obj) {
|
||||
return (string)obj.GetValue(VisualStateProperty);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
using bts.utils;
|
||||
using mbts.spark.utilities;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
@@ -10,25 +9,22 @@ using System.Windows.Documents;
|
||||
using System.Windows.Media;
|
||||
|
||||
namespace mbts.ui.utility.text {
|
||||
public enum FontFamily {
|
||||
Standard,
|
||||
Unknown
|
||||
}
|
||||
|
||||
static class TextUtils {
|
||||
public const string WPFNewLine = "
";
|
||||
public static readonly string StandardFontFamilyName;
|
||||
public static readonly System.Windows.Media.FontFamily StandardFontFamily;
|
||||
public static readonly FontFamily StandardFontFamily;
|
||||
public static readonly Typeface StandardTypeface;
|
||||
public static readonly GlyphTypeface StandardGlyphTypeface;
|
||||
public static readonly GlyphContext StandardGlyphContext;
|
||||
public static readonly IReadOnlyList<FontFamily> SystemFontFamilies;
|
||||
public static readonly IReadOnlyList<FontWeight> SystemFontWeights;
|
||||
|
||||
static TextUtils() {
|
||||
// try to guarentee a standard font
|
||||
var preferred_fonts = new[] {
|
||||
new System.Windows.Media.FontFamily("Segoe UI"),
|
||||
new FontFamily("Segoe UI"),
|
||||
SystemFonts.MessageFontFamily,
|
||||
(System.Windows.Media.FontFamily)TextElement.FontFamilyProperty.DefaultMetadata.DefaultValue
|
||||
(FontFamily)TextElement.FontFamilyProperty.DefaultMetadata.DefaultValue
|
||||
};
|
||||
foreach (var f in preferred_fonts) {
|
||||
StandardTypeface = new Typeface(f, FontStyles.Normal, FontWeights.Normal, FontStretches.Normal);
|
||||
@@ -39,6 +35,18 @@ namespace mbts.ui.utility.text {
|
||||
break;
|
||||
}
|
||||
}
|
||||
var families = Fonts.SystemFontFamilies.ToList();
|
||||
families.Sort(CompareUtils.safe_comparison<FontFamily>(
|
||||
(a, b) => a.FamilyNames.First().Value.CompareTo(b.FamilyNames.First().Value)));
|
||||
SystemFontFamilies = families;
|
||||
|
||||
var weights = typeof(FontWeights).GetProperties(BindingFlags.Public | BindingFlags.Static)
|
||||
.Where(p => p.PropertyType == typeof(FontWeight))
|
||||
.Select(p => (FontWeight)p.GetValue(null))
|
||||
.Distinct()
|
||||
.ToList();
|
||||
weights.Sort((x, y) => x.ToOpenTypeWeight().CompareTo(y.ToOpenTypeWeight()));
|
||||
SystemFontWeights = weights;
|
||||
}
|
||||
|
||||
public static FormattedText GetFormattedText(string text) { return GetFormattedText(text, FontSizes.Regular); }
|
||||
@@ -64,7 +72,7 @@ namespace mbts.ui.utility.text {
|
||||
public static Typeface with(this Typeface typeface, FontWeight font_weight) =>
|
||||
new Typeface(typeface.FontFamily, typeface.Style, font_weight, typeface.Stretch);
|
||||
|
||||
public static Typeface with(this Typeface typeface, System.Windows.Media.FontFamily font_family) =>
|
||||
public static Typeface with(this Typeface typeface, FontFamily font_family) =>
|
||||
new Typeface(font_family, typeface.Style, typeface.Weight, typeface.Stretch);
|
||||
|
||||
/// <summary>
|
||||
@@ -100,32 +108,26 @@ namespace mbts.ui.utility.text {
|
||||
}
|
||||
}
|
||||
|
||||
public static FontFamily ParseFontFamily(string family_name) {
|
||||
// TODO
|
||||
switch (family_name) {
|
||||
default: return FontFamily.Standard;
|
||||
}
|
||||
}
|
||||
|
||||
public static IEnumerable<FontWeight> available_weights(this System.Windows.Media.FontFamily family) =>
|
||||
public static IEnumerable<FontWeight> available_weights(this FontFamily family) =>
|
||||
family == null ? new FontWeight[] {
|
||||
FontWeights.Light,
|
||||
FontWeights.Normal,
|
||||
FontWeights.SemiBold,
|
||||
FontWeights.Bold,
|
||||
FontWeights.Heavy
|
||||
}.AsEnumerable() :
|
||||
family.FamilyTypefaces
|
||||
.Select(ft => ft.Weight)
|
||||
.Distinct()
|
||||
.OrderBy(fw => fw.ToOpenTypeWeight());
|
||||
|
||||
public static IEnumerable<FontWeight> all_font_weights() =>
|
||||
typeof(FontWeights).GetProperties(BindingFlags.Public | BindingFlags.Static)
|
||||
.Where(p => p.PropertyType == typeof(FontWeight))
|
||||
.Select(p => (FontWeight)p.GetValue(null)).Distinct();
|
||||
|
||||
|
||||
public static string name(this System.Windows.Media.FontFamily font_family) =>
|
||||
public static string name(this FontFamily font_family) =>
|
||||
font_family.FamilyNames.Values.FirstOrDefault() ?? string.Empty;
|
||||
|
||||
public static string display_name(this System.Windows.Media.FontFamily family, double size) =>
|
||||
public static string display_name(this FontFamily family, double size) =>
|
||||
$"{family}, {size} pt";
|
||||
|
||||
public static string display_name(this System.Windows.Media.FontFamily family, FontWeight weight, double size) =>
|
||||
public static string display_name(this FontFamily family, FontWeight weight, double size) =>
|
||||
$"{family} {weight}, {size} pt";
|
||||
|
||||
public static bool is_space_type(this char c) => CharSets.spaces.Contains(c);
|
||||
|
||||
@@ -376,7 +376,7 @@ namespace mbts.ui {
|
||||
switch (x_content_align) {
|
||||
case HorizontalAlignment.Left: content_left = border_thickness.Left + padding.Left; break;
|
||||
case HorizontalAlignment.Center: content_left = 0.5 * (result_width - content_width); break;
|
||||
case HorizontalAlignment.Right: content_left = result_width - border_thickness.Right - padding.Right; break;
|
||||
case HorizontalAlignment.Right: content_left = result_width - border_thickness.Right - padding.Right - content_width; break;
|
||||
default: goto case HorizontalAlignment.Center;
|
||||
}
|
||||
} else {
|
||||
@@ -408,7 +408,7 @@ namespace mbts.ui {
|
||||
switch (y_content_align) {
|
||||
case VerticalAlignment.Top: content_top = border_thickness.Top + padding.Top; break;
|
||||
case VerticalAlignment.Center: content_top = 0.5 * (result_height - content_height); break;
|
||||
case VerticalAlignment.Bottom: content_top = result_height - border_thickness.Bottom - padding.Bottom; break;
|
||||
case VerticalAlignment.Bottom: content_top = result_height - border_thickness.Bottom - padding.Bottom - content_height; break;
|
||||
default: goto case VerticalAlignment.Center;
|
||||
}
|
||||
} else {
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
namespace mbts.ui.viewmodels {
|
||||
class BooleanParameterVM : ParameterVM<bool> {
|
||||
public BooleanParameterVM(string name)
|
||||
: base(name) { }
|
||||
}
|
||||
|
||||
class BooleanOverrideParameterVM : ParameterOverrideVM<bool> {
|
||||
public BooleanOverrideParameterVM(string name)
|
||||
: base(name) { }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
using mbts.ui.controls.color_picker;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
using System.Windows.Media;
|
||||
|
||||
namespace mbts.ui.viewmodels {
|
||||
class ColorParameterVM : ParameterVM<Color> {
|
||||
ObservableCollection<ColorPaletteVM> _palettes;
|
||||
|
||||
public ColorParameterVM(string name)
|
||||
: base(name) =>
|
||||
value = Colors.Black;
|
||||
|
||||
public ObservableCollection<ColorPaletteVM> palettes => _palettes ??
|
||||
(_palettes = new ObservableCollection<ColorPaletteVM>(
|
||||
ColorPalette.all_presets()
|
||||
.Select(p => new ColorPaletteVM(p))));
|
||||
}
|
||||
|
||||
class ColorOverrideParameterVM : ParameterOverrideVM<Color> {
|
||||
ObservableCollection<ColorPaletteVM> _palettes;
|
||||
|
||||
public ColorOverrideParameterVM(string name)
|
||||
: base(name) { }
|
||||
|
||||
public override Color override_value {
|
||||
get => base.override_value;
|
||||
set => base.override_value = value;
|
||||
}
|
||||
public ObservableCollection<ColorPaletteVM> palettes =>
|
||||
_palettes ?? (_palettes = new ObservableCollection<ColorPaletteVM>(ColorPalette.all_presets().Select(p => new ColorPaletteVM(p))));
|
||||
}
|
||||
}
|
||||
Loaded 100 of 116 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user