Compare commits

...
Author SHA1 Message Date
John Colvin ede2774783 ladder settings dialog mini ob tab uses new grid settings view 2019-10-26 16:26:37 -05:00
John Colvin 9209561d20 GridSettingsVM derives from ParameterGroupVM 2019-10-26 16:26:37 -05:00
John Colvin 2157e157d3 GridColumnSettingsVM derives from ParameterGroupVM 2019-10-26 16:26:37 -05:00
John Colvin c78a13fe56 [non functional] put LadderSettingsView resources in ResourceDictionary (so GridSettingsResources can be added to 'MergedDictionaries') 2019-10-26 16:26:37 -05:00
John Colvin 8034ad2a71 make synthetic management grid settings pretty 2019-10-26 16:26:37 -05:00
John Colvin 1f6835fc53 sync pnl settings dialog title with main window 2019-10-26 16:26:37 -05:00
John Colvin 99f26667e7 OrderBook settings dialog uses FlatTabControl style 2019-10-26 16:26:37 -05:00
John Colvin a80bf72ee4 set ShellSettingsDialog [initial]MinHeight to 560 2019-10-26 16:26:37 -05:00
John Colvin 04b8f255b1 set ShellSettingsDialog [initial]MaxHeight to 700 2019-10-26 16:26:36 -05:00
John Colvin 805f6a117a add WindowMaxSizeConverter to implement 'SizeToContent' max dimensions 2019-10-26 16:26:36 -05:00
John Colvin 7b8d45c229 make pnl settings pretty 2019-10-26 16:26:36 -05:00
John Colvin 89d2652619 spruce up hamburger menu 2019-10-26 16:26:36 -05:00
John Colvin 96790ea365 GridSettingsView uses FlatTabControl style 2019-10-26 16:26:36 -05:00
John Colvin a97a1bd775 define FlatTabControl style (will be used in new settings dialogs) 2019-10-26 16:26:36 -05:00
John Colvin e69dc78969 make AppendTextConverter a MarkupExtension (allows less verbose definition in xaml) 2019-10-26 16:26:36 -05:00
John Colvin 824ff609b2 GridSettingsDialog - column listbox optionally show display name (hide by default) 2019-10-26 16:26:36 -05:00
John Colvin d5e07ae893 DragDropHelper.vertical_insertion_adorner 2019-10-26 16:26:36 -05:00
John Colvin a66e164326 add ObservableList (basic IObservableCollection impl) 2019-10-26 16:26:36 -05:00
John Colvin 822850d557 repair ObservableAggregateCollection.Aggregator 2019-10-26 16:26:36 -05:00
John Colvin a55bee9798 fix MouseTraceAdorner flicker (offset with Margin instead of RenderTransform) 2019-10-26 16:26:36 -05:00
John Colvin fb367f4b55 make time and sales settings pretty 2019-10-26 16:26:36 -05:00
John Colvin 50fd62c834 make mview settings pretty 2019-10-26 16:26:36 -05:00
John Colvin f8e929aaa8 hook up HyperlinkColumn.font_weight binding 2019-10-26 16:26:36 -05:00
John Colvin 7f61c782a7 HyperlinkColumn should bind to HorizontalContentAlignment prop (not HorizontalAlignment) 2019-10-26 16:26:36 -05:00
John Colvin 6586a884f4 fix WPFHelper.measure_visual_content - 'left edge' calculation for right align; 'bottom edge' for bottom align 2019-10-26 16:26:36 -05:00
John Colvin a7edb1785f FillsVM state doesn't need to be observable (every usage is equiv to Observable.Return) 2019-10-26 16:26:36 -05:00
John Colvin 27b7aa4bf5 ensure latest column settings retrieved from GridSettingsManager 2019-10-26 16:26:36 -05:00
John Colvin cbb1425354 FillsVM invalidates rows when settings changes (refreshes view) 2019-10-26 16:26:36 -05:00
John Colvin 2416bb376a hide column background color in Fills settings dialog (row bg indicates side) 2019-10-26 16:26:36 -05:00
John Colvin fba6bd2d97 make fills grid settings pretty 2019-10-26 16:26:36 -05:00
John Colvin fe3377cc79 add DefaultConverter prop to ValueMap 2019-10-26 16:26:36 -05:00
John Colvin 01fd435cde GlobalAlertsView - get rid of sound preview button (play when dropdown closes instead) 2019-10-26 16:26:36 -05:00
John Colvin a053695f7c OrderBook actually uses default settings by default 2019-10-26 16:26:36 -05:00
John Colvin 87b6032be7 OrderTypeItem template uses Rectangle instead of Badge (the latter has some unresolved rounding issues) 2019-10-26 16:26:36 -05:00
John Colvin 885d670490 GlyphContext - drop unneccesary namespace prefixes 2019-10-26 16:26:36 -05:00
John Colvin 366a269dc2 OrderTypeColumn uses a little padding 2019-10-26 16:26:36 -05:00
John Colvin 86b9e2e6b4 fix typo 'ImmutableFontWeight' -> 'ImmutableFontColor' 2019-10-26 16:26:36 -05:00
John Colvin d8336938e3 hook up grid background setting in orderbook 2019-10-26 16:26:35 -05:00
John Colvin 561b39dba0 DIYGrid CellRender.draw_text with layout rounding 2019-10-26 16:26:35 -05:00
John Colvin 824323f6eb add TitleCaseConverter/Template to common xaml resources 2019-10-26 16:26:35 -05:00
John Colvin d26dd10f11 add SystemColors palette to color picker gui 2019-10-26 16:26:35 -05:00
John Colvin ec4f6b16ca make GridSettings pretty again (starting with OrderBook) 2019-10-26 16:26:35 -05:00
John Colvin ecc96b24da add ShowExpanderArrow prop to ExpanderToggleButton 2019-10-26 16:26:35 -05:00
John Colvin 6109862b37 add TypeConverterWrapper 2019-10-26 16:26:35 -05:00
John Colvin aae27e4142 ReadOnlySelectorWrapper for IList 2019-10-26 16:26:35 -05:00
John Colvin 404bd9580a add 'SelectItemOnDrop' prop to DragDropListBox 2019-10-26 16:26:35 -05:00
John Colvin 03bbb8a558 add general purpose ParameterVMs (immediate use in new GridSettings design) 2019-10-26 16:26:35 -05:00
John Colvin 146dfada01 OrderBook settings migration rule to enable background color 2019-10-26 16:26:35 -05:00
John Colvin 4caedf2fee IOrderBookGridSettingsMigrationRule impl in UserSettingsHub 2019-10-26 16:26:35 -05:00
John Colvin 05112b89a9 remove default_vm from ShellSettingsDialogVM (vestige of an earlier time) 2019-10-26 16:26:35 -05:00
John Colvin 7d29ce3d46 move SystemFontFamilies/Weights lists from FontPicker -> TextUtils 2019-10-26 16:26:35 -05:00
John Colvin 09c57423a8 add general purpose DIYGridColumn that can be defined directly in xaml 2019-10-26 16:26:35 -05:00
John Colvin f61a1f94e2 Background TemplateBinding for ExpanderToggleControl 2019-10-26 16:26:35 -05:00
John Colvin 0fc5046dec strongly type AddRange extensions for IList<T> 2019-10-26 16:26:35 -05:00
John Colvin 1ec41c531b GridSettings default_font_color shouldn't be transparent 2019-10-26 16:26:35 -05:00
John Colvin 0080947156 better look add button in global alerts dialog 2019-10-26 16:26:35 -05:00
John Colvin 111c251809 get rid of dialog skirt border in global alerts dialog 2019-10-26 16:26:35 -05:00
John Colvin 9170637ac6 add LargeMenuItem/Button styles (prereq for new color picker) 2019-10-26 16:26:35 -05:00
John Colvin 696f048c0c AddBox icon 2019-10-26 16:26:35 -05:00
John Colvin e5f7247a8e add VisualState template/proxy dependency props to support visual state shared style resources 2019-10-26 16:26:35 -05:00
John Colvin 9a64b3331b MouseStateDefinition special impl for Button/MenuItem 'IsPressed' props 2019-10-26 16:26:35 -05:00
John Colvin a34483d45e add TabularColorPicker/ColorPickerSuite (more user friendly color gui) 2019-10-26 16:26:35 -05:00
John Colvin 14a2c4f76f ColorPickerControl SelectedColorChanged event 2019-10-26 16:26:35 -05:00
John Colvin cb97889d09 ColorPickerControl should not automatically bind its DataContext 2019-10-26 16:26:34 -05:00
John Colvin ac18e6a694 FrozenBrush MarkupExtension 2019-10-26 16:26:34 -05:00
John Colvin a6f9fbaa3c add ButtonStyle prop to PopupControl 2019-10-26 16:26:34 -05:00
John Colvin c66dfa85c1 assert_style_type shouldn't require exact type match, just needs to be assignable 2019-10-26 16:26:34 -05:00
John Colvin 3863140edf move ReverseComparer to spark_core 2019-10-26 16:26:34 -05:00
John Colvin 616d90d468 refactor ComparerBuilder.add -> Add to support object initializer syntax 2019-10-26 16:26:34 -05:00
John Colvin 7ee6aed043 remove unused FontFamily enum 2019-10-26 16:26:34 -05:00
John Colvin c9a10a1c37 AlgoStudio - remove algo name from buttons, dock save/revert on bottom 2019-10-26 16:26:34 -05:00
John Colvin 025b49b7cd AlgoStudio - hide vertical scrollbar 2019-10-26 16:26:34 -05:00
140 changed files with 7283 additions and 4198 deletions

No files matched your search

+4
View File
@@ -19,6 +19,7 @@ namespace mbts.ui {
$"{nameof(Smashicons)}.{name}.GeometryKey";
}
public const string
AddBoxKey = nameof(AddBoxKey),
AddBoxesGeometryKey = nameof(AddBoxesGeometryKey),
AddFileGeometryKey = nameof(AddFileGeometryKey),
AddFileThinGeometryKey = nameof(AddFileThinGeometryKey),
@@ -40,6 +41,7 @@ namespace mbts.ui {
EditDocumentAltGeometryKey = nameof(EditDocumentAltGeometryKey),
EditDocumentGeometryKey = nameof(EditDocumentGeometryKey),
GearGeometryKey = nameof(GearGeometryKey),
HamburgerMenuGeometryKey = nameof(HamburgerMenuGeometryKey),
HornGeometryKey = nameof(HornGeometryKey),
KeyGeometryKey = nameof(KeyGeometryKey),
LetterAKey = nameof(LetterAKey),
@@ -125,6 +127,7 @@ namespace mbts.ui {
public static ImageSource Accounting { get { return load_resource_and_freeze("Accounting"); } }
public static ImageSource Add { get { return load_resource_and_freeze("Add"); } }
public static ImageSource AddBox => load_resource_and_freeze(AddBoxKey);
public static ImageSource AddBoxes => create_icon(AddBoxesGeometryKey);
public static ImageSource AddEncircled { get { return load_resource_and_freeze("AddEncircled"); } }
public static ImageSource AddFile => create_icon(Smashicons.AddFileGeometryKey);
@@ -172,6 +175,7 @@ namespace mbts.ui {
public static ImageSource FilterActive { get { return load_resource_and_freeze("FilterActive"); } }
public static ImageSource FilterError { get { return load_resource_and_freeze("FilterError"); } }
public static ImageSource Gear => create_icon(GearGeometryKey);
public static ImageSource HamburgerMenu => create_icon(HamburgerMenuGeometryKey);
public static ImageSource Horn => create_icon(HornGeometryKey);
public static ImageSource Import => load_resource_and_freeze("Import");
public static ImageSource Key => create_icon(KeyGeometryKey);
+45
View File
@@ -13,6 +13,51 @@
M19.522852,25.255011L20.920847,25.256994 20.920847,29.729012 19.522852,29.729012z M22.826874,21.952002L25.989905,25.113989 25.001887,26.101996 21.838856,22.941015z M25.269894,19.506992L29.742937,19.509007 29.742937,20.906012 25.269894,20.907995z M10.519729,14.945011C11.300744,14.954991,12.079745,15.103001,12.818766,15.386997L10.878742,17.327C9.6457264,17.200016,8.368704,17.592014,7.4257004,18.532993L3.597655,22.359015C1.9326275,24.025 1.9316508,26.734991 3.597655,28.40299 5.2636887,30.068001 7.9747178,30.066017 9.6397144,28.40299L13.468767,24.575018C14.410794,23.633001,14.800783,22.355993,14.674774,21.122015L16.615807,19.181004C17.523806,21.547003,17.034817,24.325995,15.126776,26.233008L11.298761,30.061012C8.713709,32.644997 4.5236599,32.644997 1.9396465,30.061012 -0.64638097,27.475012 -0.64638097,23.286017 1.9386395,20.702002L5.7666852,16.875004C7.0777046,15.562992,8.8017227,14.922001,10.519729,14.945011z M2.1536381,10.959994L6.6257035,10.959994 6.6266801,12.359988 2.1536381,12.359988z M6.7686803,5.894014L9.9307338,9.0549947 8.9427158,10.044008 5.7806932,6.8819901z M10.976735,2.1390082L12.374761,2.1390082 12.375768,6.6130081 10.975728,6.6130081z M25.382902,0C27.076922,-1.8170738E-08 28.770941,0.6470033 30.063955,1.9389956 32.646992,4.5220043 32.646992,8.7130147 30.061941,11.297L26.234902,15.126011C24.326891,17.032993,21.546862,17.521,19.181845,16.614994L21.121868,14.674015C22.35586,14.798008,23.63389,14.407992,24.575886,13.467013L28.403933,9.6390096C30.067953,7.972994 30.069967,5.2619945 28.403933,3.5960094 26.738905,1.9310002 24.0269,1.9320073 22.361872,3.5960094L18.533827,7.4249893C17.591829,8.3659993,17.201811,9.6440145,17.32782,10.87601L15.385781,12.817996C14.477781,10.453005,14.968784,7.6730062,16.875818,5.7659928L20.702858,1.9379885C21.994864,0.64599622,23.688883,-1.8170738E-08,25.382902,0z
</sys:String>
<sys:String x:Key="{x:Static mbts:Icons.HamburgerMenuGeometryKey}">
M2.384186E-06,24L32.000004,24 32.000004,26 2.384186E-06,26z M0,11.999998L32,11.999998 32,13.999998 0,13.999998z M2.384186E-06,0L32.000004,0 32.000004,2 2.384186E-06,2z
</sys:String>
<DrawingImage x:Key="{x:Static mbts:Icons.AddBoxKey}">
<DrawingImage.Drawing>
<DrawingGroup>
<GeometryDrawing Brush="#E0E0E0">
<GeometryDrawing.Geometry>
<PathGeometry>
<PathGeometry.Figures>
<PathFigure StartPoint="0,0" IsClosed="True" IsFilled="True">
<PolyLineSegment>
<PolyLineSegment.Points>
<Point>100,0</Point>
<Point>100,100</Point>
<Point>0,100</Point>
</PolyLineSegment.Points>
</PolyLineSegment>
</PathFigure>
</PathGeometry.Figures>
</PathGeometry>
</GeometryDrawing.Geometry>
</GeometryDrawing>
<GeometryDrawing Brush="Black">
<GeometryDrawing.Pen>
<Pen Brush="Black" Thickness="4"/>
</GeometryDrawing.Pen>
<GeometryDrawing.Geometry>
<PathGeometry>
<PathGeometry.Figures>
<PathFigure StartPoint="25,50">
<LineSegment Point="75,50"/>
</PathFigure>
<PathFigure StartPoint="50,25">
<LineSegment Point="50,75"/>
</PathFigure>
</PathGeometry.Figures>
</PathGeometry>
</GeometryDrawing.Geometry>
</GeometryDrawing>
</DrawingGroup>
</DrawingImage.Drawing>
</DrawingImage>
<!--AddBoxes (the icon is free to use if we include this information in our credit section)
Source: https://www.flaticon.com/authors/gregor-cresnar
Ttile: Add free icon
@@ -116,7 +116,7 @@ namespace mbts.spark {
{ Columns.WorkingAsks.ToString(), GridColumnSettingsFeatureFlags.ImmutableFontColor },
{ Columns.WorkingBids.ToString(), GridColumnSettingsFeatureFlags.ImmutableFontColor }
};
grid_settings_manager = new SimpleGridSettingsManager<Row>(columns, col_settings_flags, row_vms);
grid_settings_manager = new GridSettingsManager(columns, col_settings_flags);
// this will initialize the immutable properties that correspond to the col_settings_flags above (e.g. Bid/Ask font_color_override, etc.)
grid_settings_manager.load_from(settings_manager.current_settings());
//
@@ -159,14 +159,18 @@ namespace mbts.spark {
ask_price_filter, ask_merged_size_filter, ask_direct_size_filter, ask_implied_size_filter,
last_price_filter, last_price_from_spread_filter, last_size_filter, day_change_filter, settlement_filter,
session_low_filter, session_high_filter, position_filter, raw_day_position_filter, working_bid_volume_filter, working_ask_volume_filter, volume_filter, security_key_filter),
settings_manager.subscribe_current().OnDispatcher().Subscribe(grid_settings_manager),
settings_manager.subscribe_current().OnDispatcher().Subscribe(s => {
grid_settings_manager.load_from(s);
foreach (var r in row_vms)
r.raise_invalidated();
}),
WorkLoop.Start("MarketView", _refresh)
);
}
public ObservableCollection<ColumnBase> columns { get; } = new ObservableCollection<ColumnBase>();
public SimpleGridSettingsManager<Row> grid_settings_manager { get; }
public GridSettingsManager grid_settings_manager { get; }
public GridSettings get_grid_save_state() {
if (_settings_manager.instance_settings == null)
@@ -7,7 +7,6 @@ using bts.utils;
using mbts.persist;
using mbts.ui;
using mbts.ui.settings;
using mbts.ui.shell;
using mbts.ui.spark;
using mbts.ui.utility;
using mbts.utils;
@@ -79,7 +78,7 @@ namespace mbts.spark {
if (match is ui.controls.DIYGrid.IKeyedColumn<MarketViewView.Columns> kcol) {
var col_comp = MarketViewVM.get_column_comparer(kcol.key);
if (col_comp != null) {
comp_builder.add(sc.is_ascending ? col_comp : col_comp.reverse());
comp_builder.Add(sc.is_ascending ? col_comp : col_comp.reverse());
} else {
Log.Ui.warn($"{_window_type} is unable to find the row comparer for the '{sc.col_name}' column.");
}
@@ -146,16 +145,17 @@ namespace mbts.spark {
SparkCommands.change_securities_command(() => (update_securities_cmd, _state.security_criteria)),
// wrap the grid settings view factory in a dialogCommand here since the vm is destroyed/recreated when the security criteria changes
SparkCommands.window_settings_command(new DialogCommand(() => {
var vm = new ShellSettingsDialogVM<GridSettings, SimpleGridSettingsVM>(
var vm = new ShellSettingsDialogVM<GridSettings, GridSettingsVM>(
_settings_manager,
us => us.market_view_grid,
UserSettings.FileExtensions.GRID,
s => _vm.grid_settings_manager.create_vm(
s => {
var gs_vm = _vm.grid_settings_manager.create_vm();
// we need to get the most up to date GridSettings when launching b/c users may
// have changed some settings on the grid directly, like column widths.
s.with(_vm.columns.Select(c => c.create()))
)
);
gs_vm.load(s.with(_vm.columns.Select(c => c.create())));
return gs_vm;
});
vm.SubscribeDisposed(literal_title_source.OnDispatcher().Subscribe(t => vm.title = t));
var dialog = new ShellSettingsDialog { DataContext = vm };
vm.RequestClose += (s, e) => WindowUtilities.force_close(dialog);
@@ -25,7 +25,6 @@ namespace mbts.spark {
readonly TimeAndSalesModel _model;
IWindowSettingsManager<GridSettings> _settings_manager;
public TimeAndSalesVM(IBus bus, IWindowSettingsManager<GridSettings> settings_manager, TimeAndSalesModel model, StartupData startup_data)
: base(RXCollection.CreateBuffer(model.trades, 1000), Enumerable.Empty<SortingCriteria>()) {
_bus = bus;
@@ -52,23 +51,26 @@ namespace mbts.spark {
columns.Add(new RenderTextColumn<Row, TimeAndSalesView.Columns>(col_key, r => r[col_key], filter, filter_template, r => _get_aggressor_brush(r))
{ id = col_id(col_key), Header = header, Width = width, sort_member_path = sort_member_path, horizontal_alignment = h_alignment }
);
// be sure to load GridSettings AFTER the columns collection has been populated, the grid settings manager looks columns by id to apply the appropriate settings,
// if the collection is not populated, the column settings will not be applied
grid_settings_manager = new SimpleGridSettingsManager<Row>(columns, new Dictionary<string, GridColumnSettingsFeatureFlags> { }, row_vms);
grid_settings_manager = new GridSettingsManager(columns);
grid_settings_manager.load_from(settings_manager.current_settings());
_resources.AddRange(
show_ladder,
DataGridFilterHelper.hook_filters(
rows_view, time_filter, security_filter, price_filter, size_filter, aggressor_filter, security_key_filter),
settings_manager.subscribe_current().OnDispatcher().Subscribe(grid_settings_manager)
);
settings_manager.subscribe_current().OnDispatcher().Subscribe(s => {
grid_settings_manager.load_from(s);
foreach (var r in row_vms)
r.raise_invalidated();
}));
rows_view.set_sort_descriptions(startup_data.sorting_criterion, columns);
}
public ObservableCollection<DIYGrid.ColumnBase> columns { get; } = new ObservableCollection<DIYGrid.ColumnBase>();
public DelegateCommandParamObserver<Row> show_ladder { get; }
public SimpleGridSettingsManager<Row> grid_settings_manager { get; }
public GridSettingsManager grid_settings_manager { get; }
public GridSettings get_grid_save_state() {
if (_settings_manager.instance_settings == null)
@@ -7,7 +7,6 @@ using mbts.co2;
using mbts.persist;
using mbts.ui;
using mbts.ui.settings;
using mbts.ui.shell;
using mbts.ui.spark;
using mbts.ui.utility;
using mbts.utils;
@@ -70,16 +69,17 @@ namespace mbts.spark {
SparkCommands.change_securities_command(() => (update_securities_cmd, _state.security_criteria)),
// wrap the grid settings view factory in a dialogCommand here since the vm is destroyed/recreated when the security criteria changes
SparkCommands.window_settings_command(new DialogCommand(() => {
var vm = new ShellSettingsDialogVM<GridSettings, SimpleGridSettingsVM>(
_settings_manager,
s => s.time_and_sales_grid,
UserSettings.FileExtensions.GRID,
s => _vm.value.grid_settings_manager.create_vm(
// we need to get the most up to date GridSettings when launching b/c users may
// have changed some settings on the grid directly, like column widths.
s.with(_vm.value.columns.Select(c => c.create()))
)
);
var vm = new ShellSettingsDialogVM<GridSettings, GridSettingsVM>(
_settings_manager,
s => s.time_and_sales_grid,
UserSettings.FileExtensions.GRID,
s => {
var gs_vm = _vm.value.grid_settings_manager.create_vm();
// we need to get the most up to date GridSettings when launching b/c users may
// have changed some settings on the grid directly, like column widths.
gs_vm.load(s.with(_vm.value.columns.Select(c => c.create())));
return gs_vm;
});
vm.SubscribeDisposed(literal_title_source.OnDispatcher().Subscribe(t => vm.title = t));
var dialog = new ShellSettingsDialog { DataContext = vm };
vm.RequestClose += (s, e) => WindowUtilities.force_close(dialog);
@@ -76,19 +76,20 @@ namespace mbts.spark {
};
return ob_settings;
}), SparkCommands.window_settings_command(new DialogCommand(() => {
var vm = new ShellSettingsDialogVM<GridSettings, SimpleGridSettingsVM>(
var dialog_vm = new ShellSettingsDialogVM<GridSettings, GridSettingsVM>(
_settings_manager,
us => us.synthetic_management_grid,
UserSettings.FileExtensions.GRID,
s => _vm.value.grid_settings_manager.create_vm(
s => {
var gs_vm = _vm.value.grid_settings_manager.create_vm();
// we need to get the most up to date GridSettings when launching b/c users may
// have changed some settings on the grid directly, like column widths.
s.with(_vm.value.columns.Select(c => c.create()))
)
);
vm.SubscribeDisposed(literal_title_source.OnDispatcher().Subscribe(t => vm.title = t));
var dialog = new ShellSettingsDialog { Title = "Synthetic Management Grid Settings", DataContext = vm };
vm.RequestClose += (s, e) => WindowUtilities.force_close(dialog);
gs_vm.load(s.with(_vm.value.columns.Select(c => c.create())));
return gs_vm;
});
dialog_vm.SubscribeDisposed(literal_title_source.OnDispatcher().Subscribe(t => dialog_vm.title = t));
var dialog = new ShellSettingsDialog { DataContext = dialog_vm };
dialog_vm.RequestClose += (s, e) => WindowUtilities.force_close(dialog);
return dialog;
}))
};
@@ -68,19 +68,23 @@ namespace mbts.spark {
Width = width
});
//
grid_settings_manager = new SimpleGridSettingsManager<Row>(columns, DictionaryHelper.empty<string, GridColumnSettingsFeatureFlags>(), row_vms);
grid_settings_manager = new GridSettingsManager(columns);
// this will initialize the immutable properties that correspond to the col_settings_flags above (e.g. Bid/Ask font_color_override, etc.)
grid_settings_manager.load_from(settings_manager.current_settings());
_resources.AddRange(
orders_model.admin_model.all_user_tuples.OnDispatcher().Subscribe(user_filter_items_observer),
DataGridFilterHelper.hook_all<SyntheticManagementDialogVM, Row>(this, rows_view),
settings_manager.subscribe_current().OnDispatcher().Subscribe(grid_settings_manager)
settings_manager.subscribe_current().OnDispatcher().Subscribe(s => {
grid_settings_manager.load_from(s);
foreach (var r in row_vms)
r.raise_invalidated();
})
);
}
public ObservableCollection<ColumnBase> columns { get; } = new ObservableCollection<ColumnBase>();
public SimpleGridSettingsManager<Row> grid_settings_manager { get; }
public GridSettingsManager grid_settings_manager { get; }
public IDataGridColumnPersistentFilter<Row, TimeFilterTuple> create_time_filter { get; } = new TimeFilterVM<Row>(r => r.create_time, nameof(Row.create_time));
public IDataGridColumnPersistentFilter<Row, string> name_filter { get; } = new RegexFilterVM<Row>(r => r.name, nameof(Row.name));
@@ -153,8 +153,13 @@
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<ListBox
x:Name="_tree_view"
Grid.RowSpan="2"
ItemsSource="{Binding definitions_view}"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
@@ -173,7 +178,7 @@
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<ScrollViewer Grid.Column="1" Grid.IsSharedSizeScope="True" Margin="8,4">
<ScrollViewer Grid.Column="1" Grid.IsSharedSizeScope="True" VerticalScrollBarVisibility="Auto" Margin="8,4,8,0">
<Grid x:Name="edit_pane">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
@@ -429,19 +434,25 @@
Margin="0,6,0,0"
Padding="12,8"
/>
<StackPanel Grid.Row="3" HorizontalAlignment="Right" Orientation="Horizontal">
<StackPanel.Margin>
<Thickness Left="{StaticResource DialogSkirtPadding.Left}" Top="{StaticResource VerticalPush2}" Right="{StaticResource DialogSkirtButtonPadding.Right}" Bottom="{StaticResource DialogSkirtPadding.Bottom}"/>
</StackPanel.Margin>
<Button ToolTipService.ShowOnDisabled="True" Margin="{StaticResource DialogSkirtButtonMargin}" Command="{Binding save_cmd}" CommandParameter="{Binding selected_definition}" Width="100" ToolTip="{Binding save_cmd_tooltip}" IsDefault="True">
<TextBlock Text="{Binding selected_definition.name, StringFormat='{}Save {0}'}"/>
</Button>
<Button ToolTipService.ShowOnDisabled="True" Margin="{StaticResource DialogSkirtButtonMargin}" Command="{Binding revert_cmd}" CommandParameter="{Binding selected_definition}" Width="100" ToolTip="{Binding revert_cmd_tooltip}">
<TextBlock Text="{Binding selected_definition.name, StringFormat='{}Revert {0}'}"/>
</Button>
</StackPanel>
</Grid>
</ScrollViewer>
<c:DialogSkirt
Grid.Row="1"
Grid.Column="1"
Background="Transparent"
BorderThickness="0"
>
<c:DialogSkirt.RightItems>
<x:Array Type="{x:Type Button}">
<Button ToolTipService.ShowOnDisabled="True" Margin="{StaticResource DialogSkirtButtonMargin}" Command="{Binding save_cmd}" CommandParameter="{Binding selected_definition}" Width="100" ToolTip="{Binding save_cmd_tooltip}" IsDefault="True">
Save Algo
</Button>
<Button ToolTipService.ShowOnDisabled="True" Margin="{StaticResource DialogSkirtButtonMargin}" Command="{Binding revert_cmd}" CommandParameter="{Binding selected_definition}" Width="100" ToolTip="{Binding revert_cmd_tooltip}">
Revert Algo
</Button>
</x:Array>
</c:DialogSkirt.RightItems>
</c:DialogSkirt>
</Grid>
</AdornerDecorator>
</UserControl>
@@ -9,6 +9,7 @@ using System.Collections.Generic;
using System.Linq;
using System.Reactive.Disposables;
using System.Reactive.Linq;
using System.Threading.Tasks;
using System.Windows;
using StartupData = bts.shell.AlgoStudioWindowFactory.StartupData;
@@ -68,23 +69,12 @@ namespace mbts.spark {
.Subscribe(sec_map => _view.Resources[ResourceKeys.Securities] = sec_map));
// add definition
_commands.Add(new SparkCommandCommand("Add Algo", Icons.AddBoxes, _vm.add_definition_cmd));
// clone definition
var clone_selected_definition_spark_cmd = new SparkCommandCommand(string.Empty, Icons.DocumentCopy, _vm.clone_selected_definition_cmd);
_commands.Add(clone_selected_definition_spark_cmd);
_resources.Add(_vm.subscribe_definition().OnDispatcher().Subscribe(
def => clone_selected_definition_spark_cmd.title = def == null ? $"Clone (None Selected)" : $"Clone '{def.name}'"));
// save
if (_ui_configuration.can_save_specific) {
var content_is_dirty_src = _vm.subscribe_definition().SelectManyInSeries(def => def?.subscribe_is_definition_dirty() ?? Observable.Return(false));
_commands.Add(new ReactiveSparkCommand(_vm.subscribe_definition().OnDispatcher().Select(c => $"Save '{c?.name}'"), Icons.DiskFile, () => {
if (_vm.selected_definition is AlgoDefinitionVM d)
_vm.save_definition(d).ignore();
}, content_is_dirty_src));
}
// save all
var save_all_cmd = new ObservantAsyncCommand(_vm.save_all);
_commands.Add(new SparkCommandCommand("Save All", Icons.Disk, save_all_cmd));
_resources.Add(_vm.subscribe_dirty().OnDispatcher().Subscribe(save_all_cmd));
_commands.Add(new SparkCommandCommand("Clone Algo", Icons.DocumentCopy, _vm.clone_selected_definition_cmd));
_commands.Add(new SparkCommandCommand("Save Algo", Icons.DiskFile, _vm.save_cmd) {
show_in_toolbar = _ui_configuration.can_save_specific
});
_commands.Add(new SparkCommandCommand("Save All", Icons.Disk, ObservantAsyncCommand.create(_vm.save_all, out var save_all_observer)));
_resources.Add(_vm.subscribe_dirty().OnDispatcher().Subscribe(save_all_observer));
// undo/redo
if (_ui_configuration.can_undo_redo) {
// undo
@@ -117,30 +107,11 @@ namespace mbts.spark {
_resources.Add(can_redo_source.Subscribe(redo_cmd));
}
// remove
var remove_spark_cmd = new ObservantSparkCommandCommand<IAlgoStudioContentVM>(Icons.Trash, v => $"Remove '{v?.name}'", v => v != null, value => {
if (value is AlgoDefinitionVM d) {
string prompt; MessageBoxImage icon;
if (d.key.mode == AlgoKey.Mode.Persisted) {
prompt = $"Are you sure you want to delete '{d.name}?'";
icon = MessageBoxImage.Question;
} else {
// algo hasn't even been created yet (just an unsaved placeholder item), no need to get user confirmation in this case.
prompt = null;
icon = default;
}
if (prompt != null) {
var resp = BTSMessageBox.Show(prompt, "Algo Studio", BTSMessageBoxButtons.Yes | BTSMessageBoxButtons.No, icon);
switch (resp) {
case BTSMessageBoxButtons.Yes: break;
case BTSMessageBoxButtons.No:
default: return;
}
}
_vm.remove_definition(d).ignore();
}
});
_commands.Add(remove_spark_cmd);
_resources.Add(_vm.subscribe_definition().OnDispatcher().Subscribe(remove_spark_cmd));
_commands.Add(new SparkCommandCommand("Remove Algo", Icons.Trash, ObservantAsyncCommand.create<AlgoDefinitionVM>(
vm => BTSMessageBox.Confirm($"Are you sure you want to delete '{vm.name}?'", Title) ? _vm.remove_definition(vm) : Task.CompletedTask,
vm => vm != null,
out var remove_cmd_observer)));
_resources.Add(_vm.subscribe_definition().OnDispatcher().Subscribe(remove_cmd_observer));
}
void _dispose_core() {
@@ -149,4 +120,4 @@ namespace mbts.spark {
Disposable.dispose(ref _vm);
}
}
}
}
@@ -38,10 +38,10 @@ namespace mbts.spark {
IWindowSettingsManager<GridSettings> _settings_manager;
int _total_row_count;
public FillsVM(IBus bus, OrdersModel model, IObservable<FillsSettings> state_obs, IWindowSettingsManager<GridSettings> settings_manager)
: this(bus, model, state_obs, _get_observable(bus, model, state_obs), settings_manager) { }
public FillsVM(IBus bus, OrdersModel model, FillsSettings settings, IWindowSettingsManager<GridSettings> settings_manager)
: this(bus, model, settings, _get_observable(bus, model, settings), settings_manager) { }
public FillsVM(IBus bus, OrdersModel model, IObservable<FillsSettings> state_obs, IObservable<TableUpdate<FillTuple, string>> fills_source, IWindowSettingsManager<GridSettings> settings_manager) {
public FillsVM(IBus bus, OrdersModel model, FillsSettings settings, IObservable<TableUpdate<FillTuple, string>> fills_source, IWindowSettingsManager<GridSettings> settings_manager) {
_model = model;
_settings_manager = settings_manager;
/// Columns
@@ -132,7 +132,10 @@ namespace mbts.spark {
// rows
_rows_manager = DIYGridRowsManager.create(FillsWindow.window_type_name, Columns.FillKey, col_tups, col_tups.WrapSelector(t => (t.col, t.key)), _get_column_comparer, fs => fs.build_predicate());
// grid settings
grid_settings_manager = new SimpleGridSettingsManager<Row>(columns, new Dictionary<string, GridColumnSettingsFeatureFlags> { }, rows);
var col_flags = EnumHelper.get_values<Columns>().ToDictionary(
c => c.ToString(),
c => GridColumnSettingsFeatureFlags.ImmutableBackgroundColor);
grid_settings_manager = new GridSettingsManager(columns, col_flags);
// this will initialize the immutable properties that correspond to the col_settings_flags above (e.g. Bid/Ask font_color_override, etc.)
if (_settings_manager != null)
grid_settings_manager.load_from(_settings_manager.current_settings());
@@ -145,6 +148,19 @@ namespace mbts.spark {
_unfiltered_fills_changed += o.OnNext;
return Disposable.Create(() => _unfiltered_fills_changed -= o.OnNext);
});
if (settings.grid != null && _settings_manager != null) {
// this means we are not using defaults,
// Note: settings_manager in turn notifies grid_settings_manager of this update
_settings_manager.instance_settings = settings.grid;
}
_rows_manager.on_settings(settings.filters ?? new FillsFilterStates {
fill_time = new TimeFilterTuple(
TimeFilterMode.CMEOpen,
TimeTextTuple.FromDateTime(DateTime.Today),
TimeTextTuple.FromDateTime(DateTime.Today.AddDays(1)))
},
settings.sorting_criterion);
// resources/subscriptions
_dispose = new CompositeDisposable(
ObservableAutoWrapper.connect(col_tups, columns, ct => ct.col),
ObservableAutoWrapper.connect_item_hooks(col_tups, ct => ct.filter as IDisposable),
@@ -154,22 +170,12 @@ namespace mbts.spark {
Set(ref _total_row_count, t.state.Count, nameof(total_row_count));
_unfiltered_fills_changed?.Invoke(t);
}),
state_obs.OnDispatcher().Subscribe(s => {
if (s.grid != null && _settings_manager != null) {
// this means we are not using defaults,
// Note: settings_manager in turn notifies grid_settings_manager of this update
_settings_manager.instance_settings = s.grid;
}
var fs = s.filters ?? new FillsFilterStates {
fill_time = new TimeFilterTuple(
TimeFilterMode.CMEOpen,
TimeTextTuple.FromDateTime(DateTime.Today),
TimeTextTuple.FromDateTime(DateTime.Today.AddDays(1)))
};
_rows_manager.on_settings(fs, s.sorting_criterion);
}),
model.subscribe_all_algos().SelectKeyValue(AlgoDataVM.from_data, d => AlgoID.from_algo_key(d.data.key)).OnDispatcher().Subscribe(root_algo_type_observer),
settings_manager == null ? Disposable.empty : settings_manager.subscribe_current().OnDispatcher().Subscribe(grid_settings_manager)
settings_manager == null ? Disposable.empty : settings_manager.subscribe_current().OnDispatcher().Subscribe(s => {
grid_settings_manager.load_from(s);
foreach (var r in rows)
r.raise_invalidated();
})
);
// local methods
void add_col<TFilter>(Columns col_key, string header, Func<Row, string> get_text, IDataGridColumnPersistentFilter<FillTuple, TFilter> filter, string filter_template, double width) =>
@@ -192,7 +198,7 @@ namespace mbts.spark {
public ObservableCollection<Row> rows { get; } = new ObservableCollection<Row>();
public int total_row_count => _total_row_count;
public SimpleGridSettingsManager<Row> grid_settings_manager { get; }
public GridSettingsManager grid_settings_manager { get; }
public GridSettings get_grid_save_state() {
if (_settings_manager.instance_settings == null)
@@ -282,6 +288,8 @@ namespace mbts.spark {
account = fill_tuple.account;
}
public event Action invalidated;
public DateTime fill_time => _fill.Timestamp.to_local_time();
public TimeInForce time_in_force => _order.time_in_force();
public OrderType order_type => _order.order_type();
@@ -310,8 +318,8 @@ namespace mbts.spark {
public override bool Equals(object obj) => Equals(obj as Row);
public override int GetHashCode() => _fill.FillKey.GetHashCode() ^ _order.Key.GetHashCode();
public event Action invalidated;
void IRowVM.raise_invalidated() => invalidated?.Invoke();
public void raise_invalidated() =>
invalidated?.Invoke();
}
}
}
}
@@ -26,7 +26,6 @@ namespace mbts.spark {
FillsView _view;
FillsSettings _state;
IReadOnlyList<SparkCommand> _commands;
IDisposable _subscriptions;
public FillsWindow(FillsWindowFactory factory, IBus bus, OrdersModel orders_model, FillsSettings state, WindowSettingsManager<GridSettings> settings_manager)
: base("Fills", bus, factory, SparkCommands.ShowFills().icon) {
@@ -36,8 +35,6 @@ namespace mbts.spark {
thread_id = config.Configuration.get<bts.config.MultiThreadedWindows>().fills ? "Fills" : null;
}
event Action<FillsSettings> _state_changed;
public override string window_type => window_type_name;
public override UIElement Content => _view;
public override IEnumerable<SparkCommand> Commands => _commands;
@@ -54,38 +51,29 @@ namespace mbts.spark {
public void thread_init() {
_init_guard.check_access();
Log.notnull(_state);
var state_src = Observable.FromEvent<Action<FillsSettings>, FillsSettings>(cb => _state_changed += cb, cb => _state_changed -= cb).StartWith(_state);
_vm = new FillsVM(_bus, _orders_model, state_src, _settings_manager);
_vm = new FillsVM(_bus, _orders_model, _state, _settings_manager);
_view = new FillsView { DataContext = _vm };
_commands = new List<SparkCommand> {
// wrap the grid settings view factory in a dialogCommand here since the vm is destroyed/recreated when the security criteria changes
SparkCommands.window_settings_command(new DialogCommand(() => {
var vm = new ShellSettingsDialogVM<GridSettings, SimpleGridSettingsVM>(
var vm = new ShellSettingsDialogVM<GridSettings, GridSettingsVM>(
_settings_manager,
us => us.fills_grid,
UserSettings.FileExtensions.GRID,
s => _vm.grid_settings_manager.create_vm(
s => {
var gs_vm = _vm.grid_settings_manager.create_vm();
// we need to get the most up to date GridSettings when launching b/c users may
// have changed some settings on the grid directly, like column widths.
s.with(_vm.columns.Select(c => c.create()))
)
);
gs_vm.load(s.with(_vm.columns.Select(c => c.create())));
return gs_vm;
});
vm.SubscribeDisposed(literal_title_source.OnDispatcher().Subscribe(t => vm.title = t));
var dialog = new ShellSettingsDialog { DataContext = vm };
vm.RequestClose += (s, e) => WindowUtilities.force_close(dialog);
return dialog;
}))
};
_subscriptions = Disposable.Create(() =>
_state_changed?.Invoke(
_state = new FillsSettings(_state.orders,
_vm.get_sorting_criterion().ToList(),
_vm.get_filters(),
_vm.grid_settings_manager.create()
)
)
);
}
public override void Dispose() {
@@ -6,6 +6,7 @@ using mbts.config;
using mbts.persist;
using mbts.spark;
using mbts.ui;
using mbts.ui.converters;
using mbts.ui.shell;
using mbts.ui.spark;
using ProtoBuf;
@@ -18,6 +19,7 @@ using System.Reactive.Linq;
using System.Reactive.Subjects;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Data;
using LadderSettings = mbts.spark.LadderSettings;
namespace bts.shell {
@@ -214,7 +216,21 @@ namespace bts.shell {
settings_id);
dialog_vm.applied += apply;
// dialog
var dialog = new ShellSettingsDialog { DataContext = dialog_vm };
var dialog = new ShellSettingsDialog {
DataContext = dialog_vm,
};
dialog.SetBinding(FrameworkElement.MinWidthProperty, new Binding(nameof(Window.SizeToContent)) {
Source = dialog,
Converter = new WindowMinWidthConverter {
SizeToContentThreshold = 657
}
});
dialog.SetBinding(FrameworkElement.MinHeightProperty, new Binding(nameof(Window.SizeToContent)) {
Source = dialog,
Converter = new WindowMinHeightConverter {
SizeToContentThreshold = 331
}
});
dialog_vm.SubscribeDisposed(title_source.OnDispatcher().Subscribe(t => dialog_vm.title = t));
dialog.Resources[ShellSettingsDialog.SecurityMapKey] = _securities;
dialog_vm.RequestClose += on_request_close;
@@ -12,6 +12,7 @@ using mbts.ui.controls.color_picker;
using mbts.ui.shell;
using mbts.ui.spark;
using mbts.ui.utility;
using mbts.ui.viewmodels;
using mbts.utils;
using System;
using System.Collections.Generic;
@@ -105,12 +106,13 @@ namespace mbts.spark {
};
// update
_update = new DeferrableActionWrapper(() => result_changed?.Invoke());
mini_ob.result_changed += _update.execute;
foreach (var c in _observable_components)
c.PropertyChanged += on_prop_changed;
favorite_algos_vm.items.CollectionChanged += on_favorite_algo_items_CollectionChanged;
_algo_definitions.AddRange(_model.factory.default_algos(security).Values.Select(algo_factory));
_dispose = new CompositeDisposable(
mini_ob.Observe().Subscribe(_ => _update.execute()),
ObservableAutoWrapper.connect_item_hooks(mini_ob.columns, c => c.Observe().Subscribe(_ => _update.execute())),
_model.factory.subscribe_accounts().OnDispatcher().Subscribe(acc_map => {
_accounts.SyncWithDictionary(MappedDictionary.create(acc_map, account_factory));
_try_select_desired_account();
@@ -130,7 +132,6 @@ namespace mbts.spark {
return Disposable.Create(() => item.PropertyChanged -= on_prop_changed);
}
void dispose_resources() {
mini_ob.result_changed -= _update.execute;
mini_ob.Dispose();
favorite_algos_vm.items.CollectionChanged -= on_favorite_algo_items_CollectionChanged;
favorite_algos_vm.Dispose();
@@ -163,7 +164,7 @@ namespace mbts.spark {
}
public MarketSizeDisplayVM size_display { get; } = new MarketSizeDisplayVM();
public SimpleGridSettingsVM mini_ob { get; } = new SimpleGridSettingsVM(OrderBookGridSettingsManager.hard_column_flags);
public GridSettingsVM mini_ob { get; } = new MiniOrderBookGridSettingsVM(OrderBookGridSettingsManager.hard_column_flags);
// price format
public LadderSettings.NetChangeTerms price_format { get => _price_format; set => Set(ref _price_format, value); }
public IEnumerable<LadderSettings.NetChangeTerms> price_format_options => EnumHelper.get_values<LadderSettings.NetChangeTerms>();
@@ -305,7 +306,7 @@ namespace mbts.spark {
favorite_algos_vm.load_from(settings.favorite_algos);
size_display.load_from(settings.size_display);
size_presets.SetItems(settings.calculator_buttons.empty_if_null().Select(s => _create_size_preset_vm((uint)s)));
mini_ob.load_from(settings.mini_ob.value);
mini_ob.load(settings.mini_ob.value);
// columns
_orders.assign_from(_current.orders_column);
_bid.assign_from(_current.bid_column);
@@ -363,14 +364,14 @@ namespace mbts.spark {
LadderSettings clone;
if (_current == null) {
clone = new LadderSettings {
mini_ob = new LadderMiniOrderBookSettings(mini_ob.create())
mini_ob = new LadderMiniOrderBookSettings(mini_ob.get_result())
};
} else {
clone = SparkUtils.clone_proto(_current);
#pragma warning disable CS0612 // Type or member is obsolete
clone.mini_ob = new LadderMiniOrderBookSettings(_current.mini_ob.value != null ?
mini_ob.create().with(sorting_criterion: _current.mini_ob.value.sorting_criterion) :
mini_ob.create());
mini_ob.get_result().with(sorting_criterion: _current.mini_ob.value.sorting_criterion) :
mini_ob.get_result());
#pragma warning restore CS0612 // Type or member is obsolete
}
clone.badge_style = badge_style;
@@ -810,4 +811,40 @@ namespace mbts.spark {
}
}
}
class MiniOrderBookGridSettingsVM : GridSettingsVM {
public MiniOrderBookGridSettingsVM(IReadOnlyDictionary<string, GridColumnSettingsFeatureFlags> column_flags = null)
: base(column_flags) { }
//public override void sync_params() {
// parameters.Clear();
// parameters.Add(_font_vm);
// parameters.Add(_font_color);
// if (_background_enabled)
// parameters.Add(_background);
// parameters.Add(_selection_highlight);
//}
}
//class MiniOrderBookGridSettingsVM : ParameterGroupVM {
// public MiniOrderBookGridSettingsVM(GridSettingsVM grid) { }
// MiniOrderBookGridSettingsVM(GridSettingsVM grid, ObservableAggregateCollection<object> parameters)
// : base("Mini OrderBook", parameters) {
// parameters.add_collection(grid.parameters);
// }
// public override void sync_params() {
// var fonts_and_colors = HeaderedParameterGroupVM.create("Fonts/Colors", _font_vm, _font_color);
// if (_background_enabled)
// fonts_and_colors.parameters.Add(_background);
// fonts_and_colors.parameters.Add(_selection_highlight);
// parameters.SetItems(fonts_and_colors);
// }
//}
//class GridSettingsColumnParameterGroupVM : ParameterGroupVM {
// public GridSettingsColumnParameterGroupVM(GridSettingsVM grid) : base("Columns") {
// }
//}G
}
@@ -14,255 +14,177 @@
TextElement.FontSize="{x:Static u:FontSizes.Small}"
>
<UserControl.Resources>
<sys:Double x:Key="HorizontalPush">4</sys:Double>
<sys:Double x:Key="HorizontalPush2">8</sys:Double>
<sys:Double x:Key="HorizontalPush3">12</sys:Double>
<sys:Double x:Key="IndentChild">24</sys:Double>
<sys:Double x:Key="GroupBoxOffsetLeft">8</sys:Double>
<sys:Double x:Key="GroupBoxOffsetRight">2</sys:Double>
<sys:Double x:Key="VerticalPush">4</sys:Double>
<sys:Double x:Key="VerticalSectionBreak">6</sys:Double>
<sys:Double x:Key="VerticalSplit">8</sys:Double>
<sys:Double x:Key="CollapsedSizePresetsHeight">24</sys:Double>
<!--'Ladder Columns' Column Widths-->
<sys:Double x:Key="LadderColumnsShowColumnWidth">57</sys:Double>
<sys:Double x:Key="LadderColumnsBackgroundColumnWidth">70</sys:Double>
<sys:Double x:Key="LadderColumnsForegroundColumnWidth">81</sys:Double>
<sys:Double x:Key="LadderColumnsWidthColumnWidth">50</sys:Double>
<sys:Double x:Key="LadderColumnsFontColumnWidth">160</sys:Double>
<!--Margins-->
<Thickness x:Key="MarginLPush" Left="{StaticResource HorizontalPush}"/>
<Thickness x:Key="MarginLPush2" Left="{StaticResource HorizontalPush2}"/>
<Thickness x:Key="MarginTPush" Top="{StaticResource HorizontalPush}"/>
<Thickness x:Key="MarginTPush2" Top="{StaticResource HorizontalPush2}"/>
<Thickness x:Key="MarginIndentChild" Left="{StaticResource IndentChild}"/>
<Thickness x:Key="Margin.UP1" Left="{StaticResource HorizontalPush}" Top="{StaticResource VerticalPush}" Right="{StaticResource HorizontalPush}" Bottom="{StaticResource VerticalPush}"/>
<!--Colors-->
<Color x:Key="LadderColumnDetailsBackgroundColor">LightGray</Color>
<Color x:Key="LadderColumnDetailsForegroundColor">Black</Color>
<Color x:Key="LadderColumnDetailsBorderColor">Navy</Color>
<SolidColorBrush x:Key="LadderColumnDetailsBackgroundBrush" ice:Freeze="True" Color="{StaticResource LadderColumnDetailsBackgroundColor}"/>
<SolidColorBrush x:Key="LadderColumnDetailsForegroundBrush" ice:Freeze="True" Color="{StaticResource LadderColumnDetailsForegroundColor}"/>
<SolidColorBrush x:Key="LadderColumnDetailsBorderBrush" ice:Freeze="True" Color="{StaticResource LadderColumnDetailsBorderColor}"/>
<!--Converters-->
<cv:BooleanToVisibilityConverter x:Key="NetChangeGroupBoxIsVisibleVisibilityConverter" VisibilityForTrue="Hidden" VisibilityForFalse="Visible"/>
<cv:ValueMap x:Key="ColumnSpanToVisibilityConverter" Default="{u:Visibility Visible}">
<cv:KeyValue Key="{u:Int 0}" Value="{u:Visibility Collapsed}"/>
</cv:ValueMap>
<c:BadgeTextLinesConverter x:Key="BadgeTextLinesConverter"/>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<usp:GridSettingsResources/>
</ResourceDictionary.MergedDictionaries>
<sys:Double x:Key="HorizontalPush">4</sys:Double>
<sys:Double x:Key="HorizontalPush2">8</sys:Double>
<sys:Double x:Key="HorizontalPush3">12</sys:Double>
<sys:Double x:Key="IndentChild">24</sys:Double>
<sys:Double x:Key="GroupBoxOffsetLeft">8</sys:Double>
<sys:Double x:Key="GroupBoxOffsetRight">2</sys:Double>
<sys:Double x:Key="VerticalPush">4</sys:Double>
<sys:Double x:Key="VerticalSectionBreak">6</sys:Double>
<sys:Double x:Key="VerticalSplit">8</sys:Double>
<sys:Double x:Key="CollapsedSizePresetsHeight">24</sys:Double>
<!--'Ladder Columns' Column Widths-->
<sys:Double x:Key="LadderColumnsShowColumnWidth">57</sys:Double>
<sys:Double x:Key="LadderColumnsBackgroundColumnWidth">70</sys:Double>
<sys:Double x:Key="LadderColumnsForegroundColumnWidth">81</sys:Double>
<sys:Double x:Key="LadderColumnsWidthColumnWidth">50</sys:Double>
<sys:Double x:Key="LadderColumnsFontColumnWidth">160</sys:Double>
<!--Margins-->
<Thickness x:Key="MarginLPush" Left="{StaticResource HorizontalPush}"/>
<Thickness x:Key="MarginLPush2" Left="{StaticResource HorizontalPush2}"/>
<Thickness x:Key="MarginTPush" Top="{StaticResource HorizontalPush}"/>
<Thickness x:Key="MarginTPush2" Top="{StaticResource HorizontalPush2}"/>
<Thickness x:Key="MarginIndentChild" Left="{StaticResource IndentChild}"/>
<Thickness x:Key="Margin.UP1" Left="{StaticResource HorizontalPush}" Top="{StaticResource VerticalPush}" Right="{StaticResource HorizontalPush}" Bottom="{StaticResource VerticalPush}"/>
<!--Colors-->
<Color x:Key="LadderColumnDetailsBackgroundColor">LightGray</Color>
<Color x:Key="LadderColumnDetailsForegroundColor">Black</Color>
<Color x:Key="LadderColumnDetailsBorderColor">Navy</Color>
<SolidColorBrush x:Key="LadderColumnDetailsBackgroundBrush" ice:Freeze="True" Color="{StaticResource LadderColumnDetailsBackgroundColor}"/>
<SolidColorBrush x:Key="LadderColumnDetailsForegroundBrush" ice:Freeze="True" Color="{StaticResource LadderColumnDetailsForegroundColor}"/>
<SolidColorBrush x:Key="LadderColumnDetailsBorderBrush" ice:Freeze="True" Color="{StaticResource LadderColumnDetailsBorderColor}"/>
<!--Converters-->
<cv:BooleanToVisibilityConverter x:Key="NetChangeGroupBoxIsVisibleVisibilityConverter" VisibilityForTrue="Hidden" VisibilityForFalse="Visible"/>
<cv:ValueMap x:Key="ColumnSpanToVisibilityConverter" Default="{u:Visibility Visible}">
<cv:KeyValue Key="{u:Int 0}" Value="{u:Visibility Collapsed}"/>
</cv:ValueMap>
<c:BadgeTextLinesConverter x:Key="BadgeTextLinesConverter"/>
<Style x:Key="ColumnBackground" TargetType="Rectangle">
<Setter Property="Grid.RowSpan" Value="3"/>
<Setter Property="HorizontalAlignment" Value="Stretch"/>
<Setter Property="VerticalAlignment" Value="Stretch"/>
</Style>
<Style x:Key="VolumeComponentRectangle" TargetType="Rectangle">
<Setter Property="VerticalAlignment" Value="Stretch"/>
<Setter Property="Width">
<Setter.Value>
<MultiBinding Converter="{x:Static usp:LadderSettingsView.VolumeBarWidthConverter}">
<Binding Path="(usp:LadderSettingsView.VolumeComponentValue)" RelativeSource="{RelativeSource Self}"/>
<Binding Path="DataContext.volume.max_total_volume" RelativeSource="{RelativeSource AncestorType=usp:LadderSettingsView}"/>
<Binding Path="ActualWidth" RelativeSource="{RelativeSource AncestorType=Canvas}"/>
</MultiBinding>
</Setter.Value>
</Setter>
</Style>
<Style x:Key="ColumnBackground" TargetType="Rectangle">
<Setter Property="Grid.RowSpan" Value="3"/>
<Setter Property="HorizontalAlignment" Value="Stretch"/>
<Setter Property="VerticalAlignment" Value="Stretch"/>
</Style>
<Style x:Key="VolumeComponentRectangle" TargetType="Rectangle">
<Setter Property="VerticalAlignment" Value="Stretch"/>
<Setter Property="Width">
<Setter.Value>
<MultiBinding Converter="{x:Static usp:LadderSettingsView.VolumeBarWidthConverter}">
<Binding Path="(usp:LadderSettingsView.VolumeComponentValue)" RelativeSource="{RelativeSource Self}"/>
<Binding Path="DataContext.volume.max_total_volume" RelativeSource="{RelativeSource AncestorType=usp:LadderSettingsView}"/>
<Binding Path="ActualWidth" RelativeSource="{RelativeSource AncestorType=Canvas}"/>
</MultiBinding>
</Setter.Value>
</Setter>
</Style>
<Style x:Key="LadderColumnDetailsHyperlink" TargetType="Hyperlink" BasedOn="{StaticResource {x:Type Hyperlink}}">
<Setter Property="Foreground" Value="{u:BTSColor header_text}"/>
<Setter Property="Command" Value="{Binding toggle_show_details_cmd}"/>
<Style.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Foreground" Value="Tan"/>
</Trigger>
</Style.Triggers>
</Style>
<Style x:Key="LadderColumnDetailsHyperlink" TargetType="Hyperlink" BasedOn="{StaticResource {x:Type Hyperlink}}">
<Setter Property="Foreground" Value="{u:BTSColor header_text}"/>
<Setter Property="Command" Value="{Binding toggle_show_details_cmd}"/>
<Style.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Foreground" Value="Tan"/>
</Trigger>
</Style.Triggers>
</Style>
<Style x:Key="LadderColumnLadderColumnDetailsBorder" TargetType="Border">
<Setter Property="Background" Value="{StaticResource LadderColumnDetailsBackgroundBrush}"/>
<Setter Property="BorderBrush" Value="Navy"/>
<Setter Property="BorderThickness" Value="1"/>
<Setter Property="Padding" Value="12,10,12,6"/>
<Setter Property="Visibility" Value="{Binding show_details, Converter={StaticResource BooleanToVisibilityConverter}}"/>
</Style>
<Style x:Key="LadderColumnLadderColumnDetailsBorder" TargetType="Border">
<Setter Property="Background" Value="{StaticResource LadderColumnDetailsBackgroundBrush}"/>
<Setter Property="BorderBrush" Value="Navy"/>
<Setter Property="BorderThickness" Value="1"/>
<Setter Property="Padding" Value="12,10,12,6"/>
<Setter Property="Visibility" Value="{Binding show_details, Converter={StaticResource BooleanToVisibilityConverter}}"/>
</Style>
<Style x:Key="HintText" TargetType="TextBlock">
<Setter Property="FontStyle" Value="Italic"/>
<Setter Property="Foreground" Value="Gray"/>
<Setter Property="TextWrapping" Value="Wrap"/>
</Style>
<Style x:Key="HintText" TargetType="TextBlock">
<Setter Property="FontStyle" Value="Italic"/>
<Setter Property="Foreground" Value="Gray"/>
<Setter Property="TextWrapping" Value="Wrap"/>
</Style>
<Style x:Key="LadderColumnShowToggle" TargetType="CheckBox">
<Setter Property="Grid.Column" Value="1"/>
<Setter Property="ToolTipService.ShowOnDisabled" Value="True"/>
<Setter Property="HorizontalAlignment" Value="Center"/>
<Setter Property="VerticalAlignment" Value="Center"/>
<Setter Property="IsChecked" Value="{Binding show}"/>
<Setter Property="IsEnabled" Value="{Binding can_toggle_show}"/>
<Setter Property="ToolTip">
<Setter.Value>
<MultiBinding Converter="{x:Static usp:LadderSettingsView.LadderColumnToggleShowToolTipConverter}">
<Binding Path="can_toggle_show"/>
<Binding Path="show"/>
<Binding Path="name"/>
</MultiBinding>
</Setter.Value>
</Setter>
</Style>
<Style x:Key="LadderColumnShowToggle" TargetType="CheckBox">
<Setter Property="Grid.Column" Value="1"/>
<Setter Property="ToolTipService.ShowOnDisabled" Value="True"/>
<Setter Property="HorizontalAlignment" Value="Center"/>
<Setter Property="VerticalAlignment" Value="Center"/>
<Setter Property="IsChecked" Value="{Binding show}"/>
<Setter Property="IsEnabled" Value="{Binding can_toggle_show}"/>
<Setter Property="ToolTip">
<Setter.Value>
<MultiBinding Converter="{x:Static usp:LadderSettingsView.LadderColumnToggleShowToolTipConverter}">
<Binding Path="can_toggle_show"/>
<Binding Path="show"/>
<Binding Path="name"/>
</MultiBinding>
</Setter.Value>
</Setter>
</Style>
<Style x:Key="LadderColumnColorPicker" TargetType="c:ColorPickerPopup">
<Setter Property="BorderBrush" Value="Transparent"/>
<Setter Property="BorderThickness" Value="0"/>
<Setter Property="PopupPlacement" Value="Left"/>
</Style>
<Style x:Key="LadderColumnBackgroundPicker" TargetType="c:ColorPickerPopup" BasedOn="{StaticResource LadderColumnColorPicker}">
<Setter Property="Grid.Column" Value="2"/>
<Setter Property="Palettes" Value="{Binding background_palettes}"/>
<Setter Property="SelectedColor" Value="{Binding background}"/>
</Style>
<Style x:Key="LadderColumnForegroundPicker" TargetType="c:ColorPickerPopup" BasedOn="{StaticResource LadderColumnColorPicker}">
<Setter Property="Grid.Column" Value="3"/>
<Setter Property="Palettes" Value="{Binding foreground_palettes}"/>
<Setter Property="SelectedColor" Value="{Binding foreground}"/>
</Style>
<Style x:Key="LadderColumnWidthTickUpDown" TargetType="c:TickUpDownUInt">
<Setter Property="Grid.Column" Value="4"/>
<Setter Property="Value" Value="{Binding width}"/>
</Style>
<Style x:Key="LadderColumnColorPicker" TargetType="c:ColorPickerPopup">
<Setter Property="BorderBrush" Value="Transparent"/>
<Setter Property="BorderThickness" Value="0"/>
<Setter Property="PopupPlacement" Value="Left"/>
</Style>
<Style x:Key="LadderColumnBackgroundPicker" TargetType="c:ColorPickerPopup" BasedOn="{StaticResource LadderColumnColorPicker}">
<Setter Property="Grid.Column" Value="2"/>
<Setter Property="Palettes" Value="{Binding background_palettes}"/>
<Setter Property="SelectedColor" Value="{Binding background}"/>
</Style>
<Style x:Key="LadderColumnForegroundPicker" TargetType="c:ColorPickerPopup" BasedOn="{StaticResource LadderColumnColorPicker}">
<Setter Property="Grid.Column" Value="3"/>
<Setter Property="Palettes" Value="{Binding foreground_palettes}"/>
<Setter Property="SelectedColor" Value="{Binding foreground}"/>
</Style>
<Style x:Key="LadderColumnWidthTickUpDown" TargetType="c:TickUpDownUInt">
<Setter Property="Grid.Column" Value="4"/>
<Setter Property="Value" Value="{Binding width}"/>
</Style>
<Style x:Key="LadderColumnFontPicker" TargetType="c:PopupControl">
<Setter Property="Grid.Column" Value="5"/>
<Setter Property="BorderBrush" Value="Transparent"/>
<Setter Property="BorderThickness" Value="0"/>
<Setter Property="PopupPlacement" Value="Left"/>
<Setter Property="ShowExpanderArrow" Value="False"/>
<Setter Property="Content" Value="{Binding}"/>
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<c:FontPicker SelectedFontSize="{Binding font_size}" Typeface="{Binding typeface}"/>
</DataTemplate>
</Setter.Value>
</Setter>
<Setter Property="Header" Value="{Binding}"/>
<Setter Property="HeaderTemplate">
<Setter.Value>
<DataTemplate>
<TextBlock
FontFamily="{Binding font_family}"
FontSize="{Binding font_size}"
FontWeight="{Binding font_weight}"
Margin="4,0"
Text="{Binding font_display_name}"
/>
</DataTemplate>
</Setter.Value>
</Setter>
</Style>
<Style x:Key="LadderColumnSample" TargetType="Border">
<Setter Property="Grid.Column" Value="6"/>
<Setter Property="Background" Value="{Binding background, Converter={StaticResource ColorToBrushConverter}}"/>
<Setter Property="BorderBrush" Value="Black"/>
<Setter Property="BorderThickness" Value="2,0,0,0"/>
<Setter Property="Padding" Value="4,0"/>
</Style>
<Style x:Key="LadderColumnRowSeparator" TargetType="Rectangle">
<Setter Property="Grid.Column" Value="1"/>
<Setter Property="Grid.ColumnSpan" Value="6"/>
<Setter Property="Fill" Value="DimGray"/>
<Setter Property="Height" Value="1"/>
<Setter Property="HorizontalAlignment" Value="Stretch"/>
<Setter Property="VerticalAlignment" Value="Bottom"/>
</Style>
<u:KeyedTemplateSelector x:Key="LadderColumnTemplateSelector">
<u:KeyedTemplateSelector.Default>
<DataTemplate>
<Grid Height="{Binding DataContext.row_height, Mode=OneWay, RelativeSource={RelativeSource AncestorType=usp:LadderSettingsView}}">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" SharedSizeGroup="c0"/>
<ColumnDefinition Width="{Binding Source={StaticResource LadderColumnsShowColumnWidth}}" SharedSizeGroup="c1"/>
<ColumnDefinition Width="{Binding Source={StaticResource LadderColumnsBackgroundColumnWidth}}" SharedSizeGroup="c2"/>
<ColumnDefinition Width="{Binding Source={StaticResource LadderColumnsForegroundColumnWidth}}" SharedSizeGroup="c3"/>
<ColumnDefinition Width="{Binding Source={StaticResource LadderColumnsWidthColumnWidth}}" SharedSizeGroup="c4"/>
<ColumnDefinition Width="{Binding Source={StaticResource LadderColumnsFontColumnWidth}}" SharedSizeGroup="c5"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Border Grid.Column="0" Background="{u:BTSColor header}" Padding="6,3">
<TextBlock VerticalAlignment="Center" Text="{Binding name}" Foreground="{u:BTSColor header_text}"/>
</Border>
<CheckBox Style="{StaticResource LadderColumnShowToggle}"/>
<c:ColorPickerPopup Style="{StaticResource LadderColumnBackgroundPicker}"/>
<c:ColorPickerPopup Style="{StaticResource LadderColumnForegroundPicker}"/>
<c:TickUpDownUInt Style="{StaticResource LadderColumnWidthTickUpDown}"/>
<c:PopupControl Style="{StaticResource LadderColumnFontPicker}"/>
<Border Style="{StaticResource LadderColumnSample}">
<Style x:Key="LadderColumnFontPicker" TargetType="c:PopupControl">
<Setter Property="Grid.Column" Value="5"/>
<Setter Property="BorderBrush" Value="Transparent"/>
<Setter Property="BorderThickness" Value="0"/>
<Setter Property="PopupPlacement" Value="Left"/>
<Setter Property="ShowExpanderArrow" Value="False"/>
<Setter Property="Content" Value="{Binding}"/>
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<c:FontPicker SelectedFontSize="{Binding font_size}" Typeface="{Binding typeface}"/>
</DataTemplate>
</Setter.Value>
</Setter>
<Setter Property="Header" Value="{Binding}"/>
<Setter Property="HeaderTemplate">
<Setter.Value>
<DataTemplate>
<TextBlock
HorizontalAlignment="Center"
VerticalAlignment="Center"
FontFamily="{Binding font_family}"
FontSize="{Binding font_size}"
FontWeight="{Binding font_weight}"
Foreground="{Binding foreground, Converter={StaticResource ColorToBrushConverter}}"
Text="{Binding sample_text}"
Margin="4,0"
Text="{Binding font_display_name}"
/>
</Border>
<Rectangle Style="{StaticResource LadderColumnRowSeparator}"/>
</Grid>
</DataTemplate>
</u:KeyedTemplateSelector.Default>
<!--Orders-->
<u:TemplateSelectorKeyValue Key="{x:Static usp:LadderSettingsView.OrdersColumnTemplateKey}">
<DataTemplate>
<Grid Height="{Binding DataContext.row_height, Mode=OneWay, RelativeSource={RelativeSource AncestorType=usp:LadderSettingsView}}">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" SharedSizeGroup="c0"/>
<ColumnDefinition Width="{Binding Source={StaticResource LadderColumnsShowColumnWidth}}" SharedSizeGroup="c1"/>
<ColumnDefinition Width="{Binding Source={StaticResource LadderColumnsBackgroundColumnWidth}}" SharedSizeGroup="c2"/>
<ColumnDefinition Width="{Binding Source={StaticResource LadderColumnsForegroundColumnWidth}}" SharedSizeGroup="c3"/>
<ColumnDefinition Width="{Binding Source={StaticResource LadderColumnsWidthColumnWidth}}" SharedSizeGroup="c4"/>
<ColumnDefinition Width="{Binding Source={StaticResource LadderColumnsFontColumnWidth}}" SharedSizeGroup="c5"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Border Grid.Column="0" Background="{u:BTSColor header}" Padding="6,3">
<TextBlock VerticalAlignment="Center" Text="{Binding name}" Foreground="{u:BTSColor header_text}"/>
</Border>
<CheckBox Style="{StaticResource LadderColumnShowToggle}"/>
<c:ColorPickerPopup Style="{StaticResource LadderColumnBackgroundPicker}"/>
<c:ColorPickerPopup Style="{StaticResource LadderColumnForegroundPicker}"/>
<c:TickUpDownUInt Style="{StaticResource LadderColumnWidthTickUpDown}"/>
<c:PopupControl Style="{StaticResource LadderColumnFontPicker}"/>
<Border Style="{StaticResource LadderColumnSample}">
<c:Badge
Background="{Binding badge_background}"
BorderBrush="{Binding badge_border}"
FontFamily="{Binding font_family}"
FontSize="{Binding font_size}"
FontWeight="{Binding font_weight}"
Foreground="{Binding foreground, Converter={StaticResource ColorToBrushConverter}}"
Width="{x:Static c:Badge.DefaultWidth}"
>
<c:Badge.TextLines>
<MultiBinding Converter="{StaticResource BadgeTextLinesConverter}">
<Binding Path="sample_side"/>
<Binding Path="sample_quantity"/>
<Binding Path="sample_filled"/>
<Binding Path="DataContext.badge_style" RelativeSource="{RelativeSource AncestorType=usp:LadderSettingsView}"/>
</MultiBinding>
</c:Badge.TextLines>
</c:Badge>
</Border>
<Rectangle Style="{StaticResource LadderColumnRowSeparator}"/>
</Grid>
</DataTemplate>
</u:TemplateSelectorKeyValue>
<!--Last-->
<u:TemplateSelectorKeyValue Key="{x:Static usp:LadderSettingsView.LastColumnTemplateKey}">
<DataTemplate>
<StackPanel>
</DataTemplate>
</Setter.Value>
</Setter>
</Style>
<Style x:Key="LadderColumnSample" TargetType="Border">
<Setter Property="Grid.Column" Value="6"/>
<Setter Property="Background" Value="{Binding background, Converter={StaticResource ColorToBrushConverter}}"/>
<Setter Property="BorderBrush" Value="Black"/>
<Setter Property="BorderThickness" Value="2,0,0,0"/>
<Setter Property="Padding" Value="4,0"/>
</Style>
<Style x:Key="LadderColumnRowSeparator" TargetType="Rectangle">
<Setter Property="Grid.Column" Value="1"/>
<Setter Property="Grid.ColumnSpan" Value="6"/>
<Setter Property="Fill" Value="DimGray"/>
<Setter Property="Height" Value="1"/>
<Setter Property="HorizontalAlignment" Value="Stretch"/>
<Setter Property="VerticalAlignment" Value="Bottom"/>
</Style>
<u:KeyedTemplateSelector x:Key="LadderColumnTemplateSelector">
<u:KeyedTemplateSelector.Default>
<DataTemplate>
<Grid Height="{Binding DataContext.row_height, Mode=OneWay, RelativeSource={RelativeSource AncestorType=usp:LadderSettingsView}}">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" SharedSizeGroup="c0"/>
@@ -271,21 +193,10 @@
<ColumnDefinition Width="{Binding Source={StaticResource LadderColumnsForegroundColumnWidth}}" SharedSizeGroup="c3"/>
<ColumnDefinition Width="{Binding Source={StaticResource LadderColumnsWidthColumnWidth}}" SharedSizeGroup="c4"/>
<ColumnDefinition Width="{Binding Source={StaticResource LadderColumnsFontColumnWidth}}" SharedSizeGroup="c5"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Border Grid.Column="0" Background="{u:BTSColor header}" Padding="6,3">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" SharedSizeGroup="name_col"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<TextBlock VerticalAlignment="Center" Text="{Binding name}" Foreground="{u:BTSColor header_text}"/>
<TextBlock Grid.Column="1" Margin="4,0,2,0">
<Hyperlink Style="{StaticResource LadderColumnDetailsHyperlink}">
(Advanced)
</Hyperlink>
</TextBlock>
</Grid>
<TextBlock VerticalAlignment="Center" Text="{Binding name}" Foreground="{u:BTSColor header_text}"/>
</Border>
<CheckBox Style="{StaticResource LadderColumnShowToggle}"/>
<c:ColorPickerPopup Style="{StaticResource LadderColumnBackgroundPicker}"/>
@@ -305,21 +216,11 @@
</Border>
<Rectangle Style="{StaticResource LadderColumnRowSeparator}"/>
</Grid>
<Border Style="{StaticResource LadderColumnLadderColumnDetailsBorder}">
<CheckBox
Content="Show Last on Volume Column"
DataContext="{Binding DataContext, RelativeSource={RelativeSource AncestorType=usp:LadderSettingsView}}"
IsChecked="{Binding volume.show_last_trade}"
Visibility="{Binding last_column.show_details, Converter={StaticResource BooleanToVisibilityConverter}}"
/>
</Border>
</StackPanel>
</DataTemplate>
</u:TemplateSelectorKeyValue>
<!--Volume-->
<u:TemplateSelectorKeyValue Key="{x:Static usp:LadderSettingsView.VolumeColumnTemplateKey}">
<DataTemplate>
<StackPanel>
</DataTemplate>
</u:KeyedTemplateSelector.Default>
<!--Orders-->
<u:TemplateSelectorKeyValue Key="{x:Static usp:LadderSettingsView.OrdersColumnTemplateKey}">
<DataTemplate>
<Grid Height="{Binding DataContext.row_height, Mode=OneWay, RelativeSource={RelativeSource AncestorType=usp:LadderSettingsView}}">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" SharedSizeGroup="c0"/>
@@ -331,18 +232,7 @@
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Border Grid.Column="0" Background="{u:BTSColor header}" Padding="6,3">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" SharedSizeGroup="name_col"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<TextBlock VerticalAlignment="Center" Text="{Binding name}" Foreground="{u:BTSColor header_text}"/>
<TextBlock Grid.Column="1" Margin="4,0,2,0">
<Hyperlink Style="{StaticResource LadderColumnDetailsHyperlink}">
(Advanced)
</Hyperlink>
</TextBlock>
</Grid>
<TextBlock VerticalAlignment="Center" Text="{Binding name}" Foreground="{u:BTSColor header_text}"/>
</Border>
<CheckBox Style="{StaticResource LadderColumnShowToggle}"/>
<c:ColorPickerPopup Style="{StaticResource LadderColumnBackgroundPicker}"/>
@@ -350,114 +240,223 @@
<c:TickUpDownUInt Style="{StaticResource LadderColumnWidthTickUpDown}"/>
<c:PopupControl Style="{StaticResource LadderColumnFontPicker}"/>
<Border Style="{StaticResource LadderColumnSample}">
<Canvas DataContext="{Binding DataContext.volume, RelativeSource={RelativeSource AncestorType=usp:LadderSettingsView}}" HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
<Grid Height="{Binding ActualHeight, RelativeSource={RelativeSource AncestorType=Canvas}}">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Rectangle
Grid.ColumnSpan="4"
usp:LadderSettingsView.VolumeComponentValue="{Binding total.sample_data.total}"
Fill="{Binding total.bars.value, Converter={StaticResource ColorToBrushConverter}}"
Visibility="{Binding total.bars.enabled, Converter={StaticResource BooleanToVisibilityConverter}}"
Style="{StaticResource VolumeComponentRectangle}"
/>
<Rectangle
usp:LadderSettingsView.VolumeComponentValue="{Binding buy.sample_data.buy}"
Fill="{Binding buy.bars.value, Converter={StaticResource ColorToBrushConverter}}"
Visibility="{Binding buy.bars.enabled, Converter={StaticResource BooleanToVisibilityConverter}}"
Style="{StaticResource VolumeComponentRectangle}"
/>
<Rectangle
Grid.Column="1"
usp:LadderSettingsView.VolumeComponentValue="{Binding sell.sample_data.sell}"
Fill="{Binding sell.bars.value, Converter={StaticResource ColorToBrushConverter}}"
Visibility="{Binding sell.bars.enabled, Converter={StaticResource BooleanToVisibilityConverter}}"
Style="{StaticResource VolumeComponentRectangle}"
/>
<Rectangle
Grid.Column="2"
usp:LadderSettingsView.VolumeComponentValue="{Binding passive.sample_data.passive}"
Fill="{Binding passive.bars.value, Converter={StaticResource ColorToBrushConverter}}"
Visibility="{Binding passive.bars.enabled, Converter={StaticResource BooleanToVisibilityConverter}}"
Style="{StaticResource VolumeComponentRectangle}"
/>
</Grid>
<Border Width="{Binding ActualWidth, RelativeSource={RelativeSource AncestorType=Canvas}}" Height="{Binding ActualHeight, RelativeSource={RelativeSource AncestorType=Canvas}}">
<TextBlock
Grid.ColumnSpan="4"
HorizontalAlignment="Center"
Text="{Binding passive.sample_data.numbers_text}"
Foreground="{Binding general.foreground, Converter={StaticResource ColorToBrushConverter}}"
/>
</Border>
</Canvas>
<c:Badge
Background="{Binding badge_background}"
BorderBrush="{Binding badge_border}"
FontFamily="{Binding font_family}"
FontSize="{Binding font_size}"
FontWeight="{Binding font_weight}"
Foreground="{Binding foreground, Converter={StaticResource ColorToBrushConverter}}"
Width="{x:Static c:Badge.DefaultWidth}"
>
<c:Badge.TextLines>
<MultiBinding Converter="{StaticResource BadgeTextLinesConverter}">
<Binding Path="sample_side"/>
<Binding Path="sample_quantity"/>
<Binding Path="sample_filled"/>
<Binding Path="DataContext.badge_style" RelativeSource="{RelativeSource AncestorType=usp:LadderSettingsView}"/>
</MultiBinding>
</c:Badge.TextLines>
</c:Badge>
</Border>
<Rectangle Style="{StaticResource LadderColumnRowSeparator}"/>
</Grid>
<Border Background="{StaticResource LadderColumnDetailsBackgroundBrush}" BorderBrush="{StaticResource LadderColumnDetailsBorderBrush}" BorderThickness="1" Visibility="{Binding show_details, Converter={StaticResource BooleanToVisibilityConverter}}">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="27"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Grid x:Name="volume_headers" TextElement.Foreground="{StaticResource LadderColumnDetailsForegroundBrush}">
<Grid.ColumnDefinitions>
<ColumnDefinition SharedSizeGroup="c0"/>
<ColumnDefinition Width="Auto" SharedSizeGroup="c1"/>
<ColumnDefinition Width="Auto" SharedSizeGroup="c2"/>
<ColumnDefinition SharedSizeGroup="c3"/>
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="1" HorizontalAlignment="Center" Margin="6,0" Text="Show Bar" VerticalAlignment="Center"/>
<TextBlock Grid.Column="2" HorizontalAlignment="Center" Margin="6,0" Text="Bar Color" VerticalAlignment="Center"/>
<TextBlock Grid.Column="3" HorizontalAlignment="Center" Margin="6,0" Text="Show Number" VerticalAlignment="Center"/>
</Grid>
<ItemsControl x:Name="volume_components_items_source" Grid.Row="1" Margin="0,0,0,8" DataContext="{Binding DataContext.volume, RelativeSource={RelativeSource AncestorType=usp:LadderSettingsView}}" ItemsSource="{Binding volume_components}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Grid Height="{Binding DataContext.row_height, Mode=OneWay, RelativeSource={RelativeSource AncestorType=usp:LadderSettingsView}}" IsEnabled="{Binding show}">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" SharedSizeGroup="c0"/>
<ColumnDefinition SharedSizeGroup="c1"/>
<ColumnDefinition SharedSizeGroup="c2"/>
<ColumnDefinition SharedSizeGroup="c3"/>
</Grid.ColumnDefinitions>
<Border Grid.Column="1" Grid.ColumnSpan="5" Background="White" BorderBrush="{StaticResource LadderColumnDetailsBorderBrush}" BorderThickness="1" Margin="0,-1" IsHitTestVisible="False"/>
<TextBlock Margin="70,0,0,0" HorizontalAlignment="Left" VerticalAlignment="Center" Foreground="{StaticResource LadderColumnDetailsForegroundBrush}" Text="{Binding name}"/>
<CheckBox Grid.Column="1" HorizontalAlignment="Center" VerticalAlignment="Center" IsChecked="{Binding bars.enabled}"/>
<c:ColorPickerPopup
Grid.Column="2"
BorderBrush="Transparent"
BorderThickness="0"
PopupPlacement="Left"
Palettes="{Binding palettes}"
SelectedColor="{Binding bars.value}"
/>
<CheckBox Grid.Column="3" HorizontalAlignment="Center" VerticalAlignment="Center" IsChecked="{Binding show_numbers}"/>
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</DataTemplate>
</u:TemplateSelectorKeyValue>
<!--Last-->
<u:TemplateSelectorKeyValue Key="{x:Static usp:LadderSettingsView.LastColumnTemplateKey}">
<DataTemplate>
<StackPanel>
<Grid Height="{Binding DataContext.row_height, Mode=OneWay, RelativeSource={RelativeSource AncestorType=usp:LadderSettingsView}}">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" SharedSizeGroup="c0"/>
<ColumnDefinition Width="{Binding Source={StaticResource LadderColumnsShowColumnWidth}}" SharedSizeGroup="c1"/>
<ColumnDefinition Width="{Binding Source={StaticResource LadderColumnsBackgroundColumnWidth}}" SharedSizeGroup="c2"/>
<ColumnDefinition Width="{Binding Source={StaticResource LadderColumnsForegroundColumnWidth}}" SharedSizeGroup="c3"/>
<ColumnDefinition Width="{Binding Source={StaticResource LadderColumnsWidthColumnWidth}}" SharedSizeGroup="c4"/>
<ColumnDefinition Width="{Binding Source={StaticResource LadderColumnsFontColumnWidth}}" SharedSizeGroup="c5"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Border Grid.Column="0" Background="{u:BTSColor header}" Padding="6,3">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" SharedSizeGroup="name_col"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<TextBlock VerticalAlignment="Center" Text="{Binding name}" Foreground="{u:BTSColor header_text}"/>
<TextBlock Grid.Column="1" Margin="4,0,2,0">
<Hyperlink Style="{StaticResource LadderColumnDetailsHyperlink}">
(Advanced)
</Hyperlink>
</TextBlock>
</Grid>
</Border>
<CheckBox Style="{StaticResource LadderColumnShowToggle}"/>
<c:ColorPickerPopup Style="{StaticResource LadderColumnBackgroundPicker}"/>
<c:ColorPickerPopup Style="{StaticResource LadderColumnForegroundPicker}"/>
<c:TickUpDownUInt Style="{StaticResource LadderColumnWidthTickUpDown}"/>
<c:PopupControl Style="{StaticResource LadderColumnFontPicker}"/>
<Border Style="{StaticResource LadderColumnSample}">
<TextBlock
HorizontalAlignment="Center"
VerticalAlignment="Center"
FontFamily="{Binding font_family}"
FontSize="{Binding font_size}"
FontWeight="{Binding font_weight}"
Foreground="{Binding foreground, Converter={StaticResource ColorToBrushConverter}}"
Text="{Binding sample_text}"
/>
</Border>
<Rectangle Style="{StaticResource LadderColumnRowSeparator}"/>
</Grid>
</Border>
</StackPanel>
</DataTemplate>
</u:TemplateSelectorKeyValue>
</u:KeyedTemplateSelector>
<Border Style="{StaticResource LadderColumnLadderColumnDetailsBorder}">
<CheckBox
Content="Show Last on Volume Column"
DataContext="{Binding DataContext, RelativeSource={RelativeSource AncestorType=usp:LadderSettingsView}}"
IsChecked="{Binding volume.show_last_trade}"
Visibility="{Binding last_column.show_details, Converter={StaticResource BooleanToVisibilityConverter}}"
/>
</Border>
</StackPanel>
</DataTemplate>
</u:TemplateSelectorKeyValue>
<!--Volume-->
<u:TemplateSelectorKeyValue Key="{x:Static usp:LadderSettingsView.VolumeColumnTemplateKey}">
<DataTemplate>
<StackPanel>
<Grid Height="{Binding DataContext.row_height, Mode=OneWay, RelativeSource={RelativeSource AncestorType=usp:LadderSettingsView}}">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" SharedSizeGroup="c0"/>
<ColumnDefinition Width="{Binding Source={StaticResource LadderColumnsShowColumnWidth}}" SharedSizeGroup="c1"/>
<ColumnDefinition Width="{Binding Source={StaticResource LadderColumnsBackgroundColumnWidth}}" SharedSizeGroup="c2"/>
<ColumnDefinition Width="{Binding Source={StaticResource LadderColumnsForegroundColumnWidth}}" SharedSizeGroup="c3"/>
<ColumnDefinition Width="{Binding Source={StaticResource LadderColumnsWidthColumnWidth}}" SharedSizeGroup="c4"/>
<ColumnDefinition Width="{Binding Source={StaticResource LadderColumnsFontColumnWidth}}" SharedSizeGroup="c5"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Border Grid.Column="0" Background="{u:BTSColor header}" Padding="6,3">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" SharedSizeGroup="name_col"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<TextBlock VerticalAlignment="Center" Text="{Binding name}" Foreground="{u:BTSColor header_text}"/>
<TextBlock Grid.Column="1" Margin="4,0,2,0">
<Hyperlink Style="{StaticResource LadderColumnDetailsHyperlink}">
(Advanced)
</Hyperlink>
</TextBlock>
</Grid>
</Border>
<CheckBox Style="{StaticResource LadderColumnShowToggle}"/>
<c:ColorPickerPopup Style="{StaticResource LadderColumnBackgroundPicker}"/>
<c:ColorPickerPopup Style="{StaticResource LadderColumnForegroundPicker}"/>
<c:TickUpDownUInt Style="{StaticResource LadderColumnWidthTickUpDown}"/>
<c:PopupControl Style="{StaticResource LadderColumnFontPicker}"/>
<Border Style="{StaticResource LadderColumnSample}">
<Canvas DataContext="{Binding DataContext.volume, RelativeSource={RelativeSource AncestorType=usp:LadderSettingsView}}" HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
<Grid Height="{Binding ActualHeight, RelativeSource={RelativeSource AncestorType=Canvas}}">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Rectangle
Grid.ColumnSpan="4"
usp:LadderSettingsView.VolumeComponentValue="{Binding total.sample_data.total}"
Fill="{Binding total.bars.value, Converter={StaticResource ColorToBrushConverter}}"
Visibility="{Binding total.bars.enabled, Converter={StaticResource BooleanToVisibilityConverter}}"
Style="{StaticResource VolumeComponentRectangle}"
/>
<Rectangle
usp:LadderSettingsView.VolumeComponentValue="{Binding buy.sample_data.buy}"
Fill="{Binding buy.bars.value, Converter={StaticResource ColorToBrushConverter}}"
Visibility="{Binding buy.bars.enabled, Converter={StaticResource BooleanToVisibilityConverter}}"
Style="{StaticResource VolumeComponentRectangle}"
/>
<Rectangle
Grid.Column="1"
usp:LadderSettingsView.VolumeComponentValue="{Binding sell.sample_data.sell}"
Fill="{Binding sell.bars.value, Converter={StaticResource ColorToBrushConverter}}"
Visibility="{Binding sell.bars.enabled, Converter={StaticResource BooleanToVisibilityConverter}}"
Style="{StaticResource VolumeComponentRectangle}"
/>
<Rectangle
Grid.Column="2"
usp:LadderSettingsView.VolumeComponentValue="{Binding passive.sample_data.passive}"
Fill="{Binding passive.bars.value, Converter={StaticResource ColorToBrushConverter}}"
Visibility="{Binding passive.bars.enabled, Converter={StaticResource BooleanToVisibilityConverter}}"
Style="{StaticResource VolumeComponentRectangle}"
/>
</Grid>
<Border Width="{Binding ActualWidth, RelativeSource={RelativeSource AncestorType=Canvas}}" Height="{Binding ActualHeight, RelativeSource={RelativeSource AncestorType=Canvas}}">
<TextBlock
Grid.ColumnSpan="4"
HorizontalAlignment="Center"
Text="{Binding passive.sample_data.numbers_text}"
Foreground="{Binding general.foreground, Converter={StaticResource ColorToBrushConverter}}"
/>
</Border>
</Canvas>
</Border>
<Rectangle Style="{StaticResource LadderColumnRowSeparator}"/>
</Grid>
<Border Background="{StaticResource LadderColumnDetailsBackgroundBrush}" BorderBrush="{StaticResource LadderColumnDetailsBorderBrush}" BorderThickness="1" Visibility="{Binding show_details, Converter={StaticResource BooleanToVisibilityConverter}}">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="27"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Grid x:Name="volume_headers" TextElement.Foreground="{StaticResource LadderColumnDetailsForegroundBrush}">
<Grid.ColumnDefinitions>
<ColumnDefinition SharedSizeGroup="c0"/>
<ColumnDefinition Width="Auto" SharedSizeGroup="c1"/>
<ColumnDefinition Width="Auto" SharedSizeGroup="c2"/>
<ColumnDefinition SharedSizeGroup="c3"/>
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="1" HorizontalAlignment="Center" Margin="6,0" Text="Show Bar" VerticalAlignment="Center"/>
<TextBlock Grid.Column="2" HorizontalAlignment="Center" Margin="6,0" Text="Bar Color" VerticalAlignment="Center"/>
<TextBlock Grid.Column="3" HorizontalAlignment="Center" Margin="6,0" Text="Show Number" VerticalAlignment="Center"/>
</Grid>
<ItemsControl x:Name="volume_components_items_source" Grid.Row="1" Margin="0,0,0,8" DataContext="{Binding DataContext.volume, RelativeSource={RelativeSource AncestorType=usp:LadderSettingsView}}" ItemsSource="{Binding volume_components}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Grid Height="{Binding DataContext.row_height, Mode=OneWay, RelativeSource={RelativeSource AncestorType=usp:LadderSettingsView}}" IsEnabled="{Binding show}">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" SharedSizeGroup="c0"/>
<ColumnDefinition SharedSizeGroup="c1"/>
<ColumnDefinition SharedSizeGroup="c2"/>
<ColumnDefinition SharedSizeGroup="c3"/>
</Grid.ColumnDefinitions>
<Border Grid.Column="1" Grid.ColumnSpan="5" Background="White" BorderBrush="{StaticResource LadderColumnDetailsBorderBrush}" BorderThickness="1" Margin="0,-1" IsHitTestVisible="False"/>
<TextBlock Margin="70,0,0,0" HorizontalAlignment="Left" VerticalAlignment="Center" Foreground="{StaticResource LadderColumnDetailsForegroundBrush}" Text="{Binding name}"/>
<CheckBox Grid.Column="1" HorizontalAlignment="Center" VerticalAlignment="Center" IsChecked="{Binding bars.enabled}"/>
<c:ColorPickerPopup
Grid.Column="2"
BorderBrush="Transparent"
BorderThickness="0"
PopupPlacement="Left"
Palettes="{Binding palettes}"
SelectedColor="{Binding bars.value}"
/>
<CheckBox Grid.Column="3" HorizontalAlignment="Center" VerticalAlignment="Center" IsChecked="{Binding show_numbers}"/>
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid>
</Border>
</StackPanel>
</DataTemplate>
</u:TemplateSelectorKeyValue>
</u:KeyedTemplateSelector>
</ResourceDictionary>
</UserControl.Resources>
<TabControl>
<TabControl.ItemContainerStyle>
<Style TargetType="TabItem" BasedOn="{StaticResource {x:Type TabItem}}">
<Setter Property="VerticalAlignment" Value="Stretch"/>
<Setter Property="VerticalContentAlignment" Value="Top"/>
</Style>
</TabControl.ItemContainerStyle>
<TabControl BorderThickness="1,1,1,0" ItemContainerStyle="{StaticResource FlatTabItem}" Style="{StaticResource FlatTabControl}">
<!-- Ladder Columns Tab -->
<TabItem Header="Ladder Columns" VerticalAlignment="Stretch">
<Grid Grid.IsSharedSizeScope="True" Margin="8,0">
<TabItem Header="Ladder Columns">
<Grid Grid.IsSharedSizeScope="True">
<Grid.RowDefinitions>
<RowDefinition Height="27"/>
<RowDefinition Height="Auto"/>
@@ -504,7 +503,7 @@
</Grid>
</TabItem>
<!-- Market Break Lines Tab -->
<TabItem Header="Market Break Lines" VerticalAlignment="Stretch">
<TabItem Header="Price Lines">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="27"/>
@@ -623,14 +622,14 @@
</Grid>
</TabItem>
<!-- Market Price & Size Tab -->
<TabItem Header="Market Price and Size Display">
<TabItem Header="Price &amp; Size Display">
<Grid Margin="6" Grid.IsSharedSizeScope="True">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="{Binding Source={StaticResource HorizontalPush3}}"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<GroupBox Header="Size Display" DataContext="{Binding size_display}" Padding="4">
<GroupBox Header="Size Display" DataContext="{Binding size_display}" Padding="4">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
@@ -644,7 +643,7 @@
ItemsSource="{Binding format_options}"
SelectedItem="{Binding selected_format}"
/>
<StackPanel Grid.Row="1" Grid.ColumnSpan="2" Orientation="Horizontal">
<StackPanel Grid.Row="1" Grid.ColumnSpan="2" Orientation="Horizontal" VerticalAlignment="Top">
<TextBlock
Margin="{StaticResource MarginIndentChild}"
Text="when implied is > 0"
@@ -720,8 +719,8 @@
</Grid>
</TabItem>
<!-- Size Presets Tab-->
<TabItem Header="Order Quantities" VerticalAlignment="Stretch">
<Grid HorizontalAlignment="Left" Margin="6">
<TabItem Header="Quantity Buttons">
<Grid HorizontalAlignment="Left" Margin="6" VerticalAlignment="Top">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="{Binding Source={StaticResource VerticalPush}}" MaxHeight="{Binding Source={StaticResource VerticalPush}}"/>
@@ -777,7 +776,7 @@
</Grid>
</TabItem>
<!-- Algo Favorites-->
<TabItem Header="Algo Favorites" VerticalAlignment="Stretch">
<TabItem Header="Algo Favorites">
<Grid HorizontalAlignment="Left" Margin="{StaticResource Margin.UP1}" DataContext="{Binding favorite_algos_vm}">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
@@ -815,7 +814,7 @@
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
<CheckBox Grid.Column="2" VerticalAlignment="Center" VerticalContentAlignment="Center" IsChecked="{Binding is_sticky}"> Stay Enabled?</CheckBox>
<CheckBox Grid.Column="2" VerticalAlignment="Center" VerticalContentAlignment="Center" IsChecked="{Binding is_sticky}">Stay Enabled?</CheckBox>
</Grid>
</DataTemplate>
</u:KeyedTemplateSelector.Default>
@@ -837,7 +836,7 @@
</Grid>
</TabItem>
<!-- Order Entry Panel Item Control Visbility -->
<TabItem Header="Left Panel" VerticalAlignment="Stretch">
<TabItem Header="Left Panel">
<Grid Margin="6">
<Grid.Resources>
<sys:Double x:Key="ValueRowHeight">22</sys:Double>
@@ -911,7 +910,8 @@
<!--Account-->
<TextBlock Grid.Row="4" Grid.Column="0" VerticalAlignment="Center">Account</TextBlock>
<ComboBox Grid.Row="4" Grid.Column="2" ItemsSource="{Binding accounts_view}" SelectedItem="{Binding default_account, Delay=1}"/>
<!--Time In Force-->
<!--Time In Force-->
<TextBlock Grid.Row="6" Grid.Column="0" VerticalAlignment="Center">Time In Force</TextBlock>
<ComboBox Grid.Row="6" Grid.Column="2" ItemsSource="{Binding time_in_force_options}" SelectedItem="{Binding default_time_in_force, Delay=1}"/>
<!--Algo-->
@@ -964,30 +964,9 @@
</ItemsControl.ItemTemplate>
</ItemsControl>
</GroupBox>
<!--
<ItemsControl ItemsSource="{Binding order_entry_panel_items}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<UniformGrid Columns="2" Focusable="False"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="52"/>
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="0" VerticalAlignment="Center" Text="{Binding name}"/>
<CheckBox Grid.Column="1" HorizontalAlignment="Center" VerticalAlignment="Center" IsChecked="{Binding show}"/>
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
-->
</Grid>
</TabItem>
<TabItem Header="Safety Checks" VerticalAlignment="Stretch">
<TabItem Header="Safety Checks">
<Grid HorizontalAlignment="Left" Margin="6">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
@@ -1008,8 +987,28 @@
</CheckBox>
</Grid>
</TabItem>
<TabItem Header="Mini Order Book">
<usp:GridSettingsView Height="790" Margin="6" DataContext="{Binding mini_ob}" GridSectionContent="{Binding}" GridSectionContentTemplate="{DynamicResource GridSectionCoreContentTemplate}"/>
<TabItem Header="Order Popup">
<TabControl BorderThickness="0,1,1,1" Margin="0,8,8,8" TabStripPlacement="Right" Style="{StaticResource FlatTabControl}">
<TabControl.ItemContainerStyle>
<Style TargetType="TabItem" BasedOn="{StaticResource FlatTabItem}">
<Setter Property="HorizontalAlignment" Value="Left"/>
<Style.Triggers>
<Trigger Property="IsSelected" Value="True">
<Setter Property="MinWidth" Value="66"/>
</Trigger>
</Style.Triggers>
</Style>
</TabControl.ItemContainerStyle>
<TabItem Header="Grid">
<ItemsControl Grid.IsSharedSizeScope="True" ItemsSource="{Binding mini_ob.parameters}"/>
</TabItem>
<TabItem
Header="Columns"
Content="{Binding mini_ob}"
ContentTemplate="{StaticResource ColumnsTabContent}"
Margin="0,-1,0,0"
/>
</TabControl>
</TabItem>
</TabControl>
</UserControl>
</UserControl>
@@ -29,6 +29,7 @@ using static mbts.ui.controls.DIYGrid;
using OrderType = bts.api.orderman.CustomOrderDefinition.CustomOneofCase;
using System.Windows.Input;
using System.Reactive.Disposables;
using mbts.ui.dpi;
namespace mbts.spark {
// This interface is used to hide ColumnBase.SortDirection, and offer a SortDirection member that does NOT require a DependencyProperty lookup.
@@ -46,10 +47,10 @@ namespace mbts.spark {
public OrderBookSortingData(IEnumerable<ICVOrderBookColumn> column_sorting_hierarchy) {
var comp_builder = new ComparerBuilder<OrderBookRowVM>();
foreach (var col in column_sorting_hierarchy)
comp_builder.add(col.SortDirection.Value == ListSortDirection.Ascending ? col.comparer : col.comparer.reverse());
comp_builder.Add(col.SortDirection.Value == ListSortDirection.Ascending ? col.comparer : col.comparer.reverse());
if (!column_sorting_hierarchy.Any(col => col.key == Columns.OrderKey)) {
// the resultant comparer must to be 100% deterministic, as SortedList (the building block of the SortedTree) uses a BinarySearch to find/remove items and does NOT check for item-equality.
comp_builder.add(Comparer<OrderBookRowVM>.Create((l, r) => l.order_key.CompareTo(r.order_key)));
comp_builder.Add(Comparer<OrderBookRowVM>.Create((l, r) => l.order_key.CompareTo(r.order_key)));
}
comparer = comp_builder.to_comparer();
sorting_criterion = column_sorting_hierarchy.Select(col => new SortingCriteria(col.id, col.SortDirection.GetValueOrDefault() == ListSortDirection.Ascending)).ToArray();
@@ -582,7 +583,7 @@ namespace mbts.spark {
public void draw(OrderBookRowVM row, DrawingContext dc, ColumnGeometry position, double row_height) =>
CellRender.draw_text(dc, _get_glyph_context(row), font_size, _get_text(row), position, row_height,
RenderUtils.get_foreground(row, font_brush), null, horizontal_alignment, VerticalAlignment.Center, DefaultCellPadding);
RenderUtils.get_foreground(row, font_brush), background_brush, horizontal_alignment, VerticalAlignment.Center, DefaultCellPadding);
}
#region Explicit Column Defs
@@ -592,7 +593,7 @@ namespace mbts.spark {
public void draw(OrderBookRowVM row, DrawingContext dc, ColumnGeometry position, double row_height) =>
CellRender.draw_text(dc, _get_glyph_context(row), font_size, row.side.ToString(), position, row_height,
RenderUtils.get_side_brush(row), null, horizontal_alignment, VerticalAlignment.Center, DefaultCellPadding);
RenderUtils.get_side_brush(row), background_brush, horizontal_alignment, VerticalAlignment.Center, DefaultCellPadding);
}
public class SizeColumn : OrderbookColumnBase<UIntMinMaxState>, IRenderColumn<OrderBookRowVM> {
@@ -601,7 +602,7 @@ namespace mbts.spark {
public void draw(OrderBookRowVM row, DrawingContext dc, ColumnGeometry position, double row_height) =>
CellRender.draw_text(dc, _get_glyph_context(row), font_size, row.quantity.ToString(), position, row_height,
RenderUtils.get_side_brush(row), null, horizontal_alignment, VerticalAlignment.Center, DefaultCellPadding);
RenderUtils.get_side_brush(row), background_brush, horizontal_alignment, VerticalAlignment.Center, DefaultCellPadding);
}
public class FilledColumn : OrderbookColumnBase<UIntMinMaxState>, IRenderColumn<OrderBookRowVM> {
@@ -610,7 +611,7 @@ namespace mbts.spark {
public void draw(OrderBookRowVM row, DrawingContext dc, ColumnGeometry position, double row_height) =>
CellRender.draw_text(dc, _get_glyph_context(row), font_size, row.filled_quantity.ToString(), position, row_height,
RenderUtils.get_side_brush(row), null, horizontal_alignment, VerticalAlignment.Center, DefaultCellPadding);
RenderUtils.get_side_brush(row), background_brush, horizontal_alignment, VerticalAlignment.Center, DefaultCellPadding);
}
public class OrderBookOrderTypeColumn : OrderTypeColumn<Columns>, ICVOrderBookColumn {
@@ -676,7 +677,7 @@ namespace mbts.spark {
public void draw(OrderBookRowVM row, DrawingContext dc, ColumnGeometry position, double row_height) =>
CellRender.draw_text(dc, _get_glyph_context(row), font_size, row.piq_text, position, row_height,
RenderUtils.get_foreground(row, font_brush), null, HorizontalAlignment.Right, VerticalAlignment.Center, DefaultCellPadding);
RenderUtils.get_foreground(row, font_brush), background_brush, HorizontalAlignment.Right, VerticalAlignment.Center, DefaultCellPadding);
}
public class SecurityColumn : FrameworkElementColumnBase<Columns>, ICVOrderBookColumn, IFilterableColumn {
@@ -720,14 +721,23 @@ namespace mbts.spark {
security_view.SetBinding(TextBlock.FontFamilyProperty, new Binding(nameof(font_family)) { Source = this });
security_view.SetBinding(TextBlock.FontWeightProperty, _font_weight_binding);
//
var grid = new Grid { VerticalAlignment = VerticalAlignment.Center };
Grid.SetColumn(security_view, 1);
var grid = new Grid {
VerticalAlignment = VerticalAlignment.Center
};
Grid.SetColumn(toggle_button, 1);
Grid.SetColumn(security_view, 2);
grid.ColumnDefinitions.Add(new ColumnDefinition { Width = new GridLength(10.0.RoundLayoutValue(), GridUnitType.Pixel) });
grid.ColumnDefinitions.Add(new ColumnDefinition { Width = GridLength.Auto });
grid.ColumnDefinitions.Add(new ColumnDefinition { Width = new GridLength(1, GridUnitType.Star) });
grid.Children.Add(toggle_button);
grid.Children.Add(security_view);
//
return new Border { Child = grid, Margin = new Thickness(10, 0, 0, 0) };
var container = new Grid {
HorizontalAlignment = HorizontalAlignment.Stretch,
VerticalAlignment = VerticalAlignment.Stretch
};
container.SetBinding(Control.BackgroundProperty, new Binding(nameof(background_brush)) { Source = this });
container.Children.Add(grid);
return container;
}
protected override void OnSortDirectionChanged(ListSortDirection? new_value) {
@@ -141,22 +141,24 @@ namespace mbts.spark {
toggle_show_fills_cmd = new DelegateCommand(() => show_fills = !show_fills);
launch_fills_settings_cmd = concated_disposable(new DialogCommand(() => {
const string title = "Order Book 'Mini' Fills Grid";
var vm = new ShellSettingsDialogVM<GridSettings, SimpleGridSettingsVM>(
var vm = new ShellSettingsDialogVM<GridSettings, GridSettingsVM>(
_fills_settings_manager,
us => us.fills_grid,
UserSettings.FileExtensions.GRID,
s => fills_vm.value.grid_settings_manager.create_vm(
s => {
var gs_vm = fills_vm.value.grid_settings_manager.create_vm();
// we need to get the most up to date GridSettings when launching b/c users may
// have changed some settings on the grid directly, like column widths.
s.with(fills_vm.columns.Select(c => c.create()))
),
gs_vm.load(s.with(fills_vm.columns.Select(c => c.create())));
return gs_vm;
},
show_save_as_defaults: false);
// TODO: bind to main window's Title
var dialog = new ShellSettingsDialog { Title = title, DataContext = vm };
vm.RequestClose += (s, e) => WindowUtilities.force_close(dialog);
return dialog;
}));
grid_settings_manager = new OrderBookGridSettingsManager(bus, notification_service, model, rows, core_columns);
grid_settings_manager = new OrderBookGridSettingsManager(bus, notification_service, rows, core_columns);
grid_settings_manager.algo_tree_display_mode_changed += _on_filter_changed;
IDisposable attach_vm_hooks = null;
begin_attaching += on_begin_attaching;
@@ -245,7 +247,7 @@ namespace mbts.spark {
.Subscribe(is_visible => {
if (is_visible) {
var init = _cached_fills_settings.to_startup_data();
fills_vm.value = new FillsVM(bus, model, Observable.Return(init), model.on_fill_tuples.Filter(mk_fills_slave_predicate_src(), ft => ft.fill.FillKey, _ => false), _fills_settings_manager);
fills_vm.value = new FillsVM(bus, model, init, model.on_fill_tuples.Filter(mk_fills_slave_predicate_src(), ft => ft.fill.FillKey, _ => false), _fills_settings_manager);
} else if (fills_vm.value != null) {
_cached_fills_settings = _get_fills_settings(fills_vm.value);
fills_vm.value = null;
@@ -261,7 +263,8 @@ namespace mbts.spark {
}),
_columns);
if (settings_manager != null) {
concat_disposable(settings_manager.subscribe_current().OnDispatcher().Subscribe(grid_settings_manager));
concat_disposable(
settings_manager.subscribe_current().OnDispatcher().Subscribe(grid_settings_manager.load_from));
}
IObservable<Func<FillTuple, bool>> mk_fills_slave_predicate_src() =>
Observable.Create<Func<FillTuple, bool>>(o => {
@@ -176,8 +176,10 @@ namespace mbts.spark {
return _vm.grid_settings_manager.create_vm(s);
}
);
gs_vm.SubscribeDisposed(literal_title_source.OnDispatcher().Subscribe(t => gs_vm.title = t));
var dialog = new ShellSettingsDialog { DataContext = gs_vm };
gs_vm.SubscribeDisposed(Disposable.Create(
literal_title_source.OnDispatcher().Subscribe(t => gs_vm.title = t),
_bus.subscribe(SparkTags.SecuritiesTag).OnDispatcher().Subscribe(sec_map => dialog.Resources[ResourceKeys.Securities] = sec_map)));
gs_vm.RequestClose += (s, e) => WindowUtilities.force_close(dialog);
return dialog;
}));
@@ -1,15 +1,17 @@
using bts.api;
using bts.settings;
using bts.shell.orderbook;
using bts.spark;
using mbts.persist;
using mbts.spark;
using mbts.ui;
using System;
using System.ComponentModel.Composition;
using System.Reactive.Linq;
using System.Threading.Tasks;
namespace bts.shell {
internal class OrderBookWindowFactory : ShellWindowFactory<OrderBookStartupData> {
class OrderBookWindowFactory : ShellWindowFactory<OrderBookStartupData> {
readonly IBus _bus;
readonly INotificationService _notification_service;
readonly INotificationServiceDataCache _notification_service_data_cache;
@@ -26,9 +28,7 @@ namespace bts.shell {
}
public override Task<IShellWindow> load(OrderBookStartupData state) {
var settings_manager = new WindowSettingsManager<OrderBookGridSettings>(_settings_hub, state.grid_settings_wrapper(),
us => us.order_book_grid ?? OrderBookStartupData.DefaultGrid,
(us, s) => us.with(order_book_grid: s));
var settings_manager = _settings_hub.create_orderbook_settings_manager(state.grid_settings_wrapper());
var w = new OrderBookWindow(_model, this, _bus, _notification_service, state, SparkCommands.ShowOrders.icon, settings_manager.as_non_shared());
return Task.FromResult<IShellWindow>(w);
}
@@ -37,5 +37,17 @@ namespace bts.shell {
var w = await load(startup_data);
_bus.post(SparkTags.ShowWindow, w);
}
[Export(typeof(IOrderBookGridSettingsMigrationRule))]
class EnableBackgroundMigrationRule : IOrderBookGridSettingsMigrationRule {
public bool apply(OrderBookGridSettings settings, out OrderBookGridSettings new_settings) {
if (!settings.general.enable_background_color) {
new_settings = settings.with(general: settings.general.with(enable_background_color: true));
return true;
}
new_settings = default;
return false;
}
}
}
}
@@ -7,6 +7,7 @@ using ProtoBuf;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using GridSettings = mbts.ui.settings.GridSettings;
namespace bts.shell.orderbook {
@@ -42,8 +43,8 @@ namespace bts.shell.orderbook {
public IReadOnlyList<OrderRecordTuplePredicateAnimations> predicate_animations => _predicate_animations ?? (_predicate_animations = new List<OrderRecordTuplePredicateAnimations>());
IEnumerable<IPredicateAnimationsData<OrderSecurity, OrderRecordTuplePredicate>> IGridSettingsWithNotifications<OrderSecurity, OrderRecordTuplePredicate>.predicate_animations => predicate_animations;
public OrderBookGridSettings with(GridSettings general) =>
new OrderBookGridSettings(general ?? this.general, predicate_animations) {
public OrderBookGridSettings with(GridSettings general = null, IEnumerable<OrderRecordTuplePredicateAnimations> predicate_animations = null) =>
new OrderBookGridSettings(general ?? this.general, predicate_animations?.ToList() ?? this.predicate_animations) {
algo_tree_display_mode = algo_tree_display_mode
};
@@ -1,23 +1,27 @@
using bts.api;
using bts.api.orderman;
using mbts;
using mbts.spark;
using mbts.ui;
using mbts.ui.controls;
using mbts.ui.settings;
using mbts.ui.utility;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using Columns = mbts.spark.OrderBookGridView.ColumnKeys;
using CompositeDisposable = System.Reactive.Disposables.CompositeDisposable;
namespace bts.shell.orderbook {
class OrderBookGridSettingsManager : IDisposable, IObserver<OrderBookGridSettings> {
class OrderBookGridSettingsManager : IDisposable {
public static readonly IReadOnlyDictionary<string, GridColumnSettingsFeatureFlags> hard_column_flags;
readonly IObserver<OrderBookGridSettings> _core_man_as_observer;
readonly IEnumerable<OrderBookRowVM> _rows;
readonly ObservableCollection<OrderRecordTuplePredicateAnimations> _active_predicate_animations =
new ObservableCollection<OrderRecordTuplePredicateAnimations>();
IReadOnlyList<OrderRecordTuplePredicateAnimations> _predicate_animations;
AlgoTreeDisplayMode _algo_tree_display_mode;
IDisposable _sound_hooks;
static OrderBookGridSettingsManager() {
// make font-weight immutable for all columns (the font-weight rule is hard-coded: Regular for root-orders, Light for all others)
@@ -27,7 +31,7 @@ namespace bts.shell.orderbook {
// make font-color immutable for Side, Size, and Filled columns (the font-color rule is hard-coded: MarketData.bid for Buy orders, MarketData.ask for Sells)
// TODO: Remove the immutable font-color flags when cell animation is ready
foreach (var c in new[] { Columns.Side, Columns.Size, Columns.Filled }) {
col_settings_flags[c.ToString()] |= GridColumnSettingsFeatureFlags.ImmutableFontWeight;
col_settings_flags[c.ToString()] |= GridColumnSettingsFeatureFlags.ImmutableFontColor;
}
col_settings_flags[Columns.Commit.ToString()] |= GridColumnSettingsFeatureFlags.ImmutableHeader;
hard_column_flags = col_settings_flags;
@@ -36,36 +40,22 @@ namespace bts.shell.orderbook {
public OrderBookGridSettingsManager(
IBus bus,
INotificationService notification_service,
OrdersModel model,
IEnumerable<OrderBookRowVM> rows,
ObservableCollection<DIYGrid.ColumnBase> columns) {
core_manager = new GridSettingsManager<OrderBookRowVM, OrderSecurity, OrderRecordTuplePredicate, OrderBookGridSettings>(
bus, model, rows, columns,
() => new OrderRecordTuplePredicate(),
() => {
var pvm = PredicateVMWrapper.create<OrderSecurity, OrderRecordTuplePredicate, OrderRecordTuplePredicate.PredicateTypeOneofCase>(
OrderRecordTuplePredicateVMFactory.get_oneofs(bus, model), rp => rp.predicate_type_case);
pvm.selected_predicate_type = pvm.predicate_types.FirstOrDefault();
return pvm;
},
col_id => {
var pvm = PredicateVMWrapper.create<OrderSecurity, OrderRecordTuplePredicate, OrderRecordTuplePredicate.PredicateTypeOneofCase>(
OrderRecordTuplePredicateVMFactory.get_oneofs(bus, model), rp => rp.predicate_type_case);
pvm.selected_predicate_type = _corresponding_predicate_type(EnumHelper.parse_or_default<Columns>(col_id));
return pvm;
},
(gen, anims) => new OrderBookGridSettings(gen, anims.empty_if_null().Select(a => new OrderRecordTuplePredicateAnimations(a.predicate_data, a.cell_animations, a.row_animations, a.sound_notifications))) { algo_tree_display_mode = _algo_tree_display_mode },
p => p.create_predicate(notification_service.data_cache),
p => p.create_notification_predicate(notification_service.data_cache),
notification_service == null ? new Func<IPredicate<OrderSecurity>, SoundNotification, IDisposable>((p, sn) => Disposable.empty) : notification_service.link_to_order,
notification_service == null ? new Func<INotificationPredicate<OrderSecurity>, IDisposable>(t => Disposable.empty) : notification_service.link_to_order,
hard_column_flags);
_core_man_as_observer = core_manager;
_rows = rows;
core_manager = new GridSettingsManager(columns, hard_column_flags);
_sound_hooks = ObservableAutoWrapper.connect_item_hooks(
_active_predicate_animations,
a => {
var pred = a.predicate_data.create_predicate(notification_service.data_cache);
return new CompositeDisposable(
a.sound_notifications.Select(s => notification_service.link_to_order(pred, s)));
});
}
public event Action algo_tree_display_mode_changed;
public GridSettingsManager<OrderBookRowVM, OrderSecurity, OrderRecordTuplePredicate, OrderBookGridSettings> core_manager { get; }
public GridSettingsManager core_manager { get; }
public AlgoTreeDisplayMode algo_tree_display_mode {
get => _algo_tree_display_mode;
@@ -78,23 +68,29 @@ namespace bts.shell.orderbook {
}
public void load_from(OrderBookGridSettings gs) {
core_manager.load_from(gs);
core_manager.load_from(gs.general);
algo_tree_display_mode = gs.algo_tree_display_mode;
_predicate_animations = gs.predicate_animations;
_active_predicate_animations.sync_with(_predicate_animations.Where(p => p.is_enabled));
foreach (var r in _rows)
r.raise_invalidated();
}
public OrderBookGridSettings create(IEnumerable<SortingCriteria> sorting_criterion) {
var gs = core_manager.create();
gs.general = gs.general.with(sorting_criterion: sorting_criterion);
gs.algo_tree_display_mode = _algo_tree_display_mode;
return gs;
}
public OrderBookSettingsVM create_vm(OrderBookGridSettings gs) =>
new OrderBookSettingsVM(core_manager.create_vm(gs.general)) {
algo_tree_display_mode = gs.algo_tree_display_mode
public OrderBookGridSettings create(IEnumerable<SortingCriteria> sorting_criterion) =>
new OrderBookGridSettings(
core_manager.create(),
_predicate_animations.ToList()) {
algo_tree_display_mode = _algo_tree_display_mode
};
public void Dispose() => core_manager.Dispose();
public OrderBookSettingsVM create_vm(OrderBookGridSettings gs) {
var vm = new OrderBookSettingsVM(core_manager.create_vm());
vm.load(gs);
return vm;
}
public void Dispose() =>
Disposable.dispose(ref _sound_hooks);
static OrderRecordTuplePredicate.PredicateTypeOneofCase _corresponding_predicate_type(Columns col) {
switch (col) {
@@ -122,12 +118,5 @@ namespace bts.shell.orderbook {
default: return default;
}
}
void IObserver<OrderBookGridSettings>.OnNext(OrderBookGridSettings value) {
_core_man_as_observer.OnNext(value);
algo_tree_display_mode = value.algo_tree_display_mode;
}
void IObserver<OrderBookGridSettings>.OnError(Exception error) => _core_man_as_observer.OnError(error);
void IObserver<OrderBookGridSettings>.OnCompleted() => _core_man_as_observer.OnCompleted();
}
}
@@ -1,46 +1,143 @@
using bts.api;
using bts.api.orderman;
using mbts;
using mbts.spark;
using mbts.ui;
using mbts.ui.settings;
using mbts.ui.shell;
using mbts.ui.spark;
using System;
using System.Collections.Generic;
namespace bts.shell.orderbook {
class OrderBookSettingsVM : DisposableViewModelBase, IWindowSettingsVM<OrderBookGridSettings>, ITemplateSelectorKey {
AlgoTreeDisplayMode _algo_tree_display_mode;
public OrderBookSettingsVM(GridSettingsVM<OrderSecurity, OrderRecordTuplePredicate, OrderBookGridSettings> core_settings_vm) =>
this.core_settings_vm = core_settings_vm;
public OrderBookSettingsVM(
IBus bus,
OrdersModel orders_model,
Func<OrderRecordTuplePredicate> predicate_data_factory,
Func<OrderRecordTuplePredicate, INotificationPredicate<OrderSecurity>> notification_predicate_factory,
Func<IPropertyWrapperVM<OrderRecordTuplePredicate>> create_predicate_vm,
Func<string, IPropertyWrapperVM<OrderRecordTuplePredicate>> create_column_predicate_vm,
Func<GridSettings, IEnumerable<IPredicateAnimationsData<OrderSecurity, OrderRecordTuplePredicate>>, OrderBookGridSettings> create_settings_obj,
IReadOnlyDictionary<string, GridColumnSettingsFeatureFlags> column_flags = null) {
core_settings_vm = new GridSettingsVM<OrderSecurity, OrderRecordTuplePredicate, OrderBookGridSettings>(
bus, orders_model, predicate_data_factory, notification_predicate_factory, create_predicate_vm, create_column_predicate_vm, create_settings_obj, column_flags);
}
public GridSettingsVM<OrderSecurity, OrderRecordTuplePredicate, OrderBookGridSettings> core_settings_vm { get; }
public AlgoTreeDisplayMode algo_tree_display_mode { get => _algo_tree_display_mode; set => Set(ref _algo_tree_display_mode, value); }
string ITemplateSelectorKey.key => GridSettingsTemplateKeys.OrderBook;
public void load(OrderBookGridSettings grid_settings) {
core_settings_vm.load(grid_settings);
algo_tree_display_mode = grid_settings.algo_tree_display_mode;
}
public OrderBookGridSettings get_result() {
var gs = core_settings_vm.get_result();
gs.algo_tree_display_mode = algo_tree_display_mode;
return gs;
}
}
using bts.api;
using bts.utils;
using mbts;
using mbts.spark;
using mbts.ui;
using mbts.ui.shell;
using mbts.ui.spark;
using mbts.ui.viewmodels;
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Windows.Input;
namespace bts.shell.orderbook {
class OrderBookSettingsVM : DisposableViewModelBase, IWindowSettingsVM<OrderBookGridSettings>, ITemplateSelectorKey {
readonly BooleanParameterVM _show_full_algo_tree = new BooleanParameterVM("Show Full Algo Tree");
public OrderBookSettingsVM(GridSettingsVM core_settings_vm) {
this.core_settings_vm = core_settings_vm;
this.echo(_show_full_algo_tree, nameof(algo_tree_display_mode));
add_alert_cmd = new DelegateCommand(() => {
alerts.Add(new OrderAlertVM {
is_enabled = true,
predicate_type = OrderRecordTuplePredicate.PredicateTypeOneofCase.FilledEvent,
sound_type = SoundNotification.SystemSound.BellCounter
});
});
delete_alert_cmd = new DelegateCommand<OrderAlertVM>(vm => {
if (vm != null)
alerts.Remove(vm);
});
}
public GridSettingsVM core_settings_vm { get; }
public AlgoTreeDisplayMode algo_tree_display_mode {
get => _show_full_algo_tree.value ? AlgoTreeDisplayMode.FullTree : AlgoTreeDisplayMode.RootAndLeafNodes;
set => _show_full_algo_tree.value = value == AlgoTreeDisplayMode.FullTree;
}
public IEnumerable parameters {
get {
yield return _show_full_algo_tree;
}
}
public ICommand add_alert_cmd { get; }
public ICommand delete_alert_cmd { get; }
// TODO: make this generic (for now, all views orderbook settings can ignore it)
public ObservableCollection<OrderAlertVM> alerts { get; } = new ObservableCollection<OrderAlertVM>();
string ITemplateSelectorKey.key => GridSettingsTemplateKeys.OrderBook;
public void load(OrderBookGridSettings grid_settings) {
core_settings_vm.load(grid_settings.general);
algo_tree_display_mode = grid_settings.algo_tree_display_mode;
CollectionHelper.add_or_update_existing(alerts,
grid_settings.predicate_animations,
pa => {
var vm = new OrderAlertVM();
vm.load_from(pa);
return vm;
},
(vm, pa) => vm.load_from(pa));
}
public OrderBookGridSettings get_result() {
var gs = core_settings_vm.get_result();
return new OrderBookGridSettings(gs, alerts.Select(vm => vm.create())) {
algo_tree_display_mode = algo_tree_display_mode
};
}
}
class OrderAlertVM : ViewModelBase, IValueFactory<OrderRecordTuplePredicateAnimations> {
SecurityCriteria _security_criteria;
SoundNotification.SystemSound _sound_type;
OrderRecordTuplePredicate.PredicateTypeOneofCase _predicate_type;
bool _is_enabled;
public bool is_enabled { get => _is_enabled; set => Set(ref _is_enabled, value); }
public SecurityCriteria security_criteria { get => _security_criteria; set => Set(ref _security_criteria, value); }
public SoundNotification.SystemSound sound_type { get => _sound_type; set => Set(ref _sound_type, value); }
public OrderRecordTuplePredicate.PredicateTypeOneofCase predicate_type { get => _predicate_type; set => Set(ref _predicate_type, value); }
public IEnumerable<OrderRecordTuplePredicate.PredicateTypeOneofCase> predicate_types { get; } =
new[] {
OrderRecordTuplePredicate.PredicateTypeOneofCase.FilledEvent,
OrderRecordTuplePredicate.PredicateTypeOneofCase.CompletedEvent,
OrderRecordTuplePredicate.PredicateTypeOneofCase.CanceledEvent
};
public OrderRecordTuplePredicateAnimations create() {
var pred = new OrderRecordTuplePredicate();
switch (predicate_type) {
case OrderRecordTuplePredicate.PredicateTypeOneofCase.CanceledEvent:
pred.canceled_event = new OrderCanceledEventPredicate {
security = security_criteria.is_defined() ?
new SecurityPredicate { is_value = security_criteria } : null
};
break;
case OrderRecordTuplePredicate.PredicateTypeOneofCase.FilledEvent:
pred.filled_event = new OrderFilledEventPredicate {
security = security_criteria.is_defined() ?
new SecurityPredicate { is_value = security_criteria } : null
};
break;
case OrderRecordTuplePredicate.PredicateTypeOneofCase.CompletedEvent:
pred.completed_event = new OrderCompletedEventPredicate {
security = security_criteria.is_defined() ?
new SecurityPredicate { is_value = security_criteria } : null
};
break;
}
return new OrderRecordTuplePredicateAnimations(
pred, null, null,
new[] {
new SoundNotification { system_sound = sound_type }
},
is_enabled);
}
public void load_from(OrderRecordTuplePredicateAnimations value) {
if (value.sound_notifications.Count == 0)
return;
is_enabled = value.is_enabled;
var animation = value.sound_notifications[0];
var pred = value.predicate_data;
if (animation.sound_type_case == SoundNotification.SoundTypeOneofCase.System)
sound_type = animation.system_sound.Value;
if (pred != null) {
predicate_type = pred.predicate_type_case;
switch (pred.predicate_type_case) {
case OrderRecordTuplePredicate.PredicateTypeOneofCase.CanceledEvent:
security_criteria = pred.canceled_event.security?.is_value;
break;
case OrderRecordTuplePredicate.PredicateTypeOneofCase.CompletedEvent:
security_criteria = pred.completed_event.security?.is_value;
break;
case OrderRecordTuplePredicate.PredicateTypeOneofCase.FilledEvent:
security_criteria = pred.filled_event.security?.is_value;
break;
}
}
}
}
}
@@ -0,0 +1,137 @@
<UserControl
x:Class="mbts.ui.spark.OrderBookSettingsView"
x:ClassModifier="internal"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:shell="clr-namespace:bts.shell"
xmlns:orderbook="clr-namespace:bts.shell.orderbook"
xmlns:u="clr-namespace:mbts.ui"
xmlns:c="clr-namespace:mbts.ui.controls"
xmlns:usp="clr-namespace:mbts.ui.spark"
xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
Content="{Binding}"
>
<UserControl.Resources>
<usp:GridSettingsResources/>
</UserControl.Resources>
<UserControl.ContentTemplate>
<DataTemplate DataType="{x:Type orderbook:OrderBookSettingsVM}">
<Grid>
<u:BindingProxy x:Name="ActionMenuItemsViewProxy" Value="{DynamicResource ActionMenuItemsView}"/>
<c:ActionMenu ItemsSource="{Binding Path='(u:BindingProxy.Value).(CollectionViewSource.View)', ElementName=ActionMenuItemsViewProxy}">
<ItemsControl.GroupStyle>
<GroupStyle ContainerStyle="{StaticResource ComboBox.DelineatedGroupContainerStyle.DynamicFill}"/>
</ItemsControl.GroupStyle>
</c:ActionMenu>
<TabControl MinWidth="500" Margin="0,2,0,0" Style="{StaticResource FlatTabControl}">
<TabItem Header="Grid">
<ItemsControl x:Name="ParameterItemsControl" Grid.IsSharedSizeScope="True">
<ItemsControl.ItemsSource>
<CompositeCollection>
<CollectionContainer Collection="{Binding DataContext.parameters, Source={u:DirectReference ParameterItemsControl}}"/>
<CollectionContainer Collection="{Binding DataContext.core_settings_vm.parameters, Source={u:DirectReference ParameterItemsControl}}"/>
</CompositeCollection>
</ItemsControl.ItemsSource>
</ItemsControl>
</TabItem>
<TabItem Header="Columns" Content="{Binding core_settings_vm}" ContentTemplate="{StaticResource ColumnsTabContent}"/>
<TabItem Header="Alerts" DataContext="{Binding DataContext.instance_vm, RelativeSource={RelativeSource AncestorType=usp:ShellSettingsDialog}}">
<Grid VerticalAlignment="Top">
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<ScrollViewer HorizontalScrollBarVisibility="Hidden" VerticalScrollBarVisibility="Auto">
<c:DIYGrid
Rows="{Binding alerts}"
BorderBrush="Black"
BorderThickness="1"
RowsHorizontalScrollBarVisibility="Collapsed"
SelectionMode="Single"
>
<c:DIYGrid.Columns>
<c:DIYGridColumnCollection>
<c:DIYGridColumn key="Delete" HeaderContainerStyle="{StaticResource DisabledColumnHeader}" Width="23" is_visible="True"/>
<c:DIYGridColumn key="IsEnabled" HeaderContainerStyle="{StaticResource DisabledColumnHeader}" Width="66" is_visible="True"/>
<c:DIYGridColumn key="Security" Header="Security" Width="130" is_visible="True"/>
<c:DIYGridColumn key="EventType" Header="Event Type" Width="120" is_visible="True"/>
<c:DIYGridColumn key="Sound" Header="Sound" Width="130" is_visible="True"/>
</c:DIYGridColumnCollection>
</c:DIYGrid.Columns>
<c:DIYGrid.RowTemplate>
<DataTemplate>
<Grid c:DIYGrid.SyncWithColumnGeometry="True">
<Button
c:DIYGrid.ColumnKey="Delete"
HorizontalAlignment="Center"
Command="{Binding DataContext.delete_alert_cmd, RelativeSource={RelativeSource AncestorType=c:DIYGrid}}"
CommandParameter="{Binding}"
Style="{StaticResource RoundCloseButton}"
ToolTip="Delete"
/>
<ToggleButton
c:DIYGrid.ColumnKey="IsEnabled"
IsChecked="{Binding is_enabled}"
Style="{StaticResource ToggleSwitch}"
/>
<ComboBox
c:DIYGrid.ColumnKey="EventType"
Margin="{StaticResource CellPadding}"
ItemsSource="{Binding predicate_types}"
ItemTemplate="{StaticResource EnumDescriptionTemplate}"
SelectedItem="{Binding predicate_type}"
/>
<c:ContractPickerPopup
c:DIYGrid.ColumnKey="Security"
Margin="{StaticResource CellPadding}"
AllowEmpty="True"
CloseOnOK="True"
CloseOnCancel="True"
MaxWidth="180"
DataContext="{Binding predicate}"
SecurityMap="{DynamicResource {x:Static u:ResourceKeys.Securities}}"
Header="{Binding security_criteria}"
ShowDialogSkirt="True"
ToolTip="{Binding security_criteria, Converter={x:Static c:ContractPickerPopup.SecurityCriteriaTextConverter}, Mode=OneWay}"
OKClick="{x:Static c:ContractPickerPopup.FlushSelectedCriteriaHandler}"
SelectedCriteria="{Binding security_criteria, UpdateSourceTrigger=Explicit}"
>
<c:ContractPickerPopup.HeaderTemplate>
<DataTemplate>
<TextBlock Text="{Binding Converter={x:Static c:ContractPickerPopup.SecurityCriteriaTextConverter}}" TextTrimming="CharacterEllipsis"/>
</DataTemplate>
</c:ContractPickerPopup.HeaderTemplate>
</c:ContractPickerPopup>
<ComboBox
x:Name="sound_dropdown"
c:DIYGrid.ColumnKey="Sound"
Margin="{StaticResource CellPadding}"
ItemsSource="{u:EnumBindingSource Type={u:Nested shell:SoundNotification.SystemSound}, Except=None}"
ItemTemplate="{StaticResource EnumDescriptionTemplate}"
SelectedItem="{Binding sound_type}"
>
<i:Interaction.Triggers>
<i:EventTrigger EventName="DropDownClosed">
<i:InvokeCommandAction Command="{x:Static c:SoundAnimationView.PreviewSoundCommand}" CommandParameter="{Binding SelectedItem, ElementName=sound_dropdown}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</ComboBox>
<Rectangle Grid.Column="0" HorizontalAlignment="Right" Width="1" Fill="Gray"/>
<Rectangle Grid.Column="1" HorizontalAlignment="Right" Width="1" Fill="Gray"/>
<Rectangle Grid.Column="2" HorizontalAlignment="Right" Width="1" Fill="Gray"/>
<Rectangle Grid.Column="3" HorizontalAlignment="Right" Width="1" Fill="Gray"/>
</Grid>
</DataTemplate>
</c:DIYGrid.RowTemplate>
</c:DIYGrid>
</ScrollViewer>
<Button Grid.Row="1" HorizontalAlignment="Stretch" HorizontalContentAlignment="Center" VerticalAlignment="Top" VerticalContentAlignment="Center" Command="{Binding add_alert_cmd}">
<TextBlock Margin="8,4" FontWeight="SemiBold">Add Alert</TextBlock>
</Button>
</Grid>
</TabItem>
</TabControl>
</Grid>
</DataTemplate>
</UserControl.ContentTemplate>
</UserControl>
@@ -0,0 +1,35 @@
using mbts.ui.converters;
using System.Windows.Controls;
using System.Windows.Data;
namespace mbts.ui.spark {
/// <summary>
/// Interaction logic for OrderBookSettingsView.xaml
/// </summary>
partial class OrderBookSettingsView : UserControl {
public static readonly IValueConverter
DisplayNameVisualStateConverter = ValueConverter.create<bool, string>(can_override => can_override ? VisualStateNames.DisplayName.Active : VisualStateNames.DisplayName.Inactive),
ColumnVisibilityToggleVisualStateConverter = ValueConverter.create<bool, string>(can_override => can_override ? VisualStateNames.ColumnVisibilityToggle.Active : VisualStateNames.ColumnVisibilityToggle.Inactive);
public OrderBookSettingsView() =>
InitializeComponent();
public static class VisualStateNames {
public static class DisplayName {
public const string GroupName = nameof(DisplayName);
public static readonly string
Active = $"{GroupName}{VisualStates.Active}",
Inactive = $"{GroupName}{VisualStates.Inactive}";
}
public static class ColumnVisibilityToggle {
public const string GroupName = nameof(ColumnVisibilityToggle);
public static readonly string
Active = $"{GroupName}{VisualStates.Active}",
Inactive = $"{GroupName}{VisualStates.Inactive}";
}
}
}
}
@@ -1,375 +1,379 @@
using bts.api;
using bts.collections;
using bts.settings;
using bts.shell.pnl;
using bts.spark;
using bts.utils;
using mbts;
using mbts.config;
using mbts.persist;
using mbts.spark;
using mbts.ui;
using mbts.ui.controls;
using mbts.ui.settings;
using mbts.ui.spark;
using mbts.ui.utility;
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Collections.Specialized;
using System.ComponentModel;
using System.ComponentModel.Composition;
using System.Linq;
using System.Reactive.Linq;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Media;
namespace bts.shell {
class PNLWindowFactory : ShellWindowFactory<PNLSaveState> {
const string _window_type = "PNL", _window_title = "PNL";
readonly IBus _bus;
readonly OrdersModel _orders_model;
readonly UserSettingsHub _settings_hub;
readonly PNLConfig _pnl_conf;
readonly IShellWindowThreadIDProvider _thread_id_provider;
public PNLWindowFactory(IShellModule owner, IBus bus, UserSettingsHub settings_hub, OrdersModel orders_model) : base(owner, _window_type) {
_bus = bus;
_orders_model = orders_model;
_settings_hub = settings_hub;
_pnl_conf = new PNLConfig(Configuration.get<bts.config.PNLOptions>());
_thread_id_provider = ShellWindowThreadIDFactory.create(_window_type, Configuration.get<bts.config.MultiThreadedWindows>().pnl);
_dispose = bus.subscribe(SparkTags.ShowPNLTag).OnDispatcher().Subscribe(_show);
}
public override Task<IShellWindow> load(PNLSaveState state) =>
Task.FromResult(_create(state.settings));
void _show(PNLSettings settings) =>
_bus.post(SparkTags.ShowWindow, _create(settings));
IShellWindow _create(PNLSettings settings) {
var settings_manager = _settings_hub.create_pnl_settings_manager(settings);
// Need to keep a local copy of the columns, since they are defined in xaml (not the viewmodel), and get_save_state may be called on a non-dispatcher thread.
// If that happens, we would be unable to access _grid.Columns, and thus unable to get the current column settings.
// Use an ImmutableList to avoid any threading issues when responding to CollectionChanged events on the dispatcher thread.
ImmutableList<DIYGrid.ColumnBase> cached_columns = ImmutableList<DIYGrid.ColumnBase>.Empty;
return SparkWindow<PNLSaveState>.create(_window_type, _window_title, _bus, this, Icons.Accounting, _thread_id_provider.next_id(), () => {
var vm = new PNLVM(_bus, _orders_model, settings ?? PNLSettings.Default);
var content = new PNLView {
DataContext = vm
};
vm.sorting_changed += sh => {
try {
DIYGridSortHelper.set_sort_directions(sh, content._grid.Columns);
} catch { }
};
vm.SubscribeDisposed(
DIYGridSortHelper.observe_active_comparers<PNLView.Columns, PNLRowVM>(content._grid, PNLVM.get_column_comparer)
.OnDispatcher()
.Subscribe(sh => vm.sorting_hierarchy = sh.Select(s => (s.key, s.direction)).ToList()));
cached_columns = ImmutableList<DIYGrid.ColumnBase>.Empty.AddRange(content._grid.Columns);
Func<PNLSaveState> get_save_state = () => {
var cols = cached_columns.ToArray(c => c.create());
var filters = vm.get_filters_states();
var sorting_criterion = vm.sorting_hierarchy.Select(tup => new SortingCriteria(tup.key.ToString(), tup.direction == ListSortDirection.Ascending)).ToArray();
var calc_type = vm.calculation_type;
var state = settings_manager.instance_settings;
if (state == null) {
// don't use defaults if the user manipulated the window state outside of the settings dialog (e.g., resized column(s) via drag, or changed sort/filter)
var defaults = settings_manager.default_settings;
if (calc_type == defaults.calculation_type &&
defaults.sorting_criterion.SequenceEqual(sorting_criterion) &&
defaults.grid.columns.SequenceEqual(cols) && defaults.filters.safe_equals(filters)) {
// the user's current state still matches the defaults (i.e., no outside manipulation was performed); return null so this window will use the latest default settings when restored
return new PNLSaveState { use_defaults = true, settings = null };
}
state = defaults;
}
// assign latest values, as they may have be modified outside of the settings dialog (in which case the settings manager would be out of date)
state = state.with(
sorting_criterion,
vm.anchors,
filters,
state.grid.with(columns: cols, background: vm.grid_background_color.rgb_color()),
calc_type,
vm.header_row_display);
return new PNLSaveState { use_defaults = false, settings = state };
};
IEnumerable<SparkCommand> commands = new[] {
new SparkCommandCommand("Collapse All", Icons.RightArrow, new DelegateCommand(() => vm.set_all_expanded(false))),
new SparkCommandCommand("Expand All", Icons.DownArrow, new DelegateCommand(() => vm.set_all_expanded(true))),
new SparkCommandCommand("Play", Icons.Play, new DelegateCommand(() => vm.is_paused = false)) {
show_in_context_menu = _pnl_conf.show_pause_resume,
show_in_toolbar = _pnl_conf.show_pause_resume
},
new SparkCommandCommand("Pause", Icons.Pause, new DelegateCommand(() => vm.is_paused = true)) {
show_in_context_menu = _pnl_conf.show_pause_resume,
show_in_toolbar = _pnl_conf.show_pause_resume
},
SparkCommands.window_settings_command(new DialogCommand(() => {
settings_manager.instance_settings = get_save_state().settings;
var dialog_vm = new ShellSettingsDialogVM<PNLSettings, PNLSettingsVM>(
settings_manager,
us => us.pnl,
UserSettings.FileExtensions.PNL,
s => new PNLSettingsVM(s));
var dialog = new ShellSettingsDialog { DataContext = dialog_vm };
dialog_vm.RequestClose += on_request_close;
return dialog;
void on_request_close(object sender, EventArgs e) =>
WindowUtilities.force_close(dialog);
}))
};
IDisposable dispose = Disposable.Create(
settings_manager.subscribe_current().OnDispatcher().Subscribe(s => {
vm.load_from(s);
var col_setting_map = s.grid.columns.ToDictionary(c => c.id);
var fallback_family = new FontFamily(s.grid.font_family);
// group
var group_col_setting_map = s.group_columns.ToDictionary(c => c.id);
var group_appearance = s.group_cell_appearance.to_appearance();
// header
var header_col_setting_map = s.header_columns.ToDictionary(c => c.id);
var header_appearance = s.header_cell_appearance.to_appearance();
//
foreach (var c in content._grid.Columns.OfType<PNLColumn>()) {
if (col_setting_map.TryGetValue(c.id, out var cs)) {
c.load_from(cs);
}
if (group_col_setting_map.TryGetValue(c.id, out var gcs)) {
c.load_group_params(
group_appearance.font_size,
gcs.font_weight_override ?? group_appearance.font_weight,
gcs.font_color_override ?? group_appearance.foreground,
group_appearance.font_family);
}
if (header_col_setting_map.TryGetValue(c.id, out var hcs)) {
c.load_header_params(
header_appearance.font_size,
hcs.font_weight_override ?? header_appearance.font_weight,
hcs.font_color_override ?? header_appearance.foreground,
header_appearance.font_family);
}
}
CollectionHelper.sync_with_never_duplicate(content._grid.Columns, s.grid.columns, cs => {
throw new InvalidOperationException();
}, c => c.id.ToLower(), cs => cs.id.ToLower());
}),
content._grid.Columns.Subscribe((s, e) => {
switch (e.Action) {
case NotifyCollectionChangedAction.Add: add_new(); break;
case NotifyCollectionChangedAction.Remove: remove_old(); break;
case NotifyCollectionChangedAction.Replace:
case NotifyCollectionChangedAction.Move:
remove_old();
add_new();
break;
case NotifyCollectionChangedAction.Reset:
cached_columns = ImmutableList<DIYGrid.ColumnBase>.Empty.AddRange(content._grid.Columns);
break;
}
void add_new() =>
cached_columns = e.NewStartingIndex >= 0 ?
cached_columns.InsertRange(e.NewStartingIndex, e.NewItems.OfType<DIYGrid.ColumnBase>()) :
cached_columns.AddRange(e.NewItems.OfType<DIYGrid.ColumnBase>());
void remove_old() =>
cached_columns = e.OldStartingIndex >= 0 ?
cached_columns.RemoveRange(e.OldStartingIndex, e.OldItems.Count) :
cached_columns.RemoveRange(e.OldItems.OfType<DIYGrid.ColumnBase>());
}),
Disposable.wrap_collection(commands.OfType<IDisposable>()),
vm);
return (content, commands, get_save_state, dispose);
},
min_width_offset: SystemParameters.HorizontalScrollBarButtonWidth + 8,
min_height_offset: 60);
}
readonly struct PNLConfig {
public readonly bool
enable_animations,
show_pause_resume;
public PNLConfig(bts.config.PNLOptions options) {
enable_animations = options.enable_animations;
show_pause_resume = options.show_pause_resume;
}
}
[Export(typeof(IPNLSettingsMigrationRule))]
class AddMissingAnimationsMigrationRule : IPNLSettingsMigrationRule {
public bool apply(PNLSettings settings, out PNLSettings new_settings) {
if (settings != null && (
_apply(ref settings, settings.animations, PNLSettings.default_animations, (s, anims) => s.with(animations: anims)) |
_apply(ref settings, settings.group_header_animations, PNLSettings.default_group_header_animations, (s, anims) => s.with(group_header_animations: anims)) |
_apply(ref settings, settings.header_row_animations, PNLSettings.default_header_row_animations, (s, anims) => s.with(header_row_animations: anims)))) {
//
new_settings = settings;
return true;
}
new_settings = null;
return false;
}
static bool _apply(ref PNLSettings settings, IEnumerable<PNLAnimationTriggerData> actual_triggers, IEnumerable<PNLAnimationTriggerData> default_triggers, Func<PNLSettings, PNLAnimationTriggerData[], PNLSettings> set_triggers) {
if (_apply(actual_triggers, default_triggers, out var result)) {
settings = set_triggers(settings, actual_triggers.Concat(result).ToArray());
return true;
}
return false;
}
static bool _apply(IEnumerable<PNLAnimationTriggerData> actual_triggers, IEnumerable<PNLAnimationTriggerData> default_triggers, out IEnumerable<PNLAnimationTriggerData> result) {
var default_trigger_map = default_triggers.ToDictionary(t => t.expression);
var actual_trigger_map = actual_triggers.ToImmutableDictionary(t => t.expression);
var snapshot = actual_trigger_map;
foreach (var kv in snapshot) {
if (default_trigger_map.TryGetValue(kv.Key, out var default_anims)) {
// assume 1 animation per column
var actual_col_anims = kv.Value.animations.ToDictionaryCrushed(a => a.column);
var default_col_anims = default_anims.animations.ToDictionaryCrushed(a => a.column);
actual_col_anims.Difference(default_col_anims,
out var exclusive_actual,
out var exclusive_defaults,
out var diff);
var new_col_anims = exclusive_defaults.ToList();
if (new_col_anims.Count > 0) {
// add the original col animations to the new ones (combine them)
new_col_anims.AddRange(actual_col_anims);
actual_trigger_map = actual_trigger_map.SetItem(kv.Key, kv.Value.with(animations: new_col_anims.Select(col_anim => col_anim.Value).ToArray()));
}
}
}
foreach (var kv in default_trigger_map) {
if (!actual_trigger_map.ContainsKey(kv.Key))
actual_trigger_map = actual_trigger_map.Add(kv.Key, kv.Value);
}
if (actual_trigger_map != snapshot) {
result = actual_trigger_map.Values;
return true;
}
result = null;
return false;
}
}
[Export(typeof(IPNLSettingsMigrationRule))]
class AddMissingColumnsMigrationRule : IPNLSettingsMigrationRule {
public bool apply(PNLSettings settings, out PNLSettings new_settings) {
if (settings != null) {
var defaults = PNLSettings.Default;
if (_apply(ref settings, settings.grid.columns, defaults.grid.columns, (s, cols) => s.with(grid: s.grid.with(cols))) |
_apply(ref settings, settings.group_columns, defaults.group_columns, (s, cols) => s.with(group_columns: cols)) |
_apply(ref settings, settings.header_columns, defaults.header_columns, (s, cols) => s.with(header_columns: cols))) {
new_settings = settings;
return true;
}
}
new_settings = null;
return false;
}
static bool _apply(ref PNLSettings settings, IEnumerable<GridColumnSettings> actual_columns, IEnumerable<GridColumnSettings> default_columns, Func<PNLSettings, IEnumerable<GridColumnSettings>, PNLSettings> set_columns) {
if (GridSettingsHelper.add_missing_columns(actual_columns, default_columns, out var new_cols)) {
settings = set_columns(settings, new_cols);
return true;
}
return false;
}
}
[Export(typeof(IPNLSettingsMigrationRule))]
class RemoveColumnsMigrationRule : IPNLSettingsMigrationRule {
public bool apply(PNLSettings settings, out PNLSettings new_settings) {
if (settings != null) {
if (_apply(ref settings, settings.grid.columns, (s, cols) => s.with(grid: s.grid.with(cols))) |
_apply(ref settings, settings.group_columns, (s, cols) => s.with(group_columns: cols)) |
_apply(ref settings, settings.header_columns, (s, cols) => s.with(header_columns: cols))) {
new_settings = settings;
return true;
}
}
new_settings = null;
return false;
}
bool _apply(ref PNLSettings settings, IEnumerable<GridColumnSettings> columns, Func<PNLSettings, IEnumerable<GridColumnSettings>, PNLSettings> set_columns) {
if (_apply(columns, out var new_cols)) {
settings = set_columns(settings, new_cols);
return true;
}
return false;
}
bool _apply(IEnumerable<GridColumnSettings> columns, out IEnumerable<GridColumnSettings> result) {
var col_list = columns.ToList();
int orig_count = col_list.Count;
col_list.RemoveWhere(c => !Enum.TryParse<PNLView.Columns>(c.id, true, out var _));
if (col_list.Count < orig_count) {
result = col_list;
return true;
}
result = null;
return false;
}
}
[Export(typeof(IPNLSettingsMigrationRule))]
class RemoveColumnAnimationsMigrationRule : IPNLSettingsMigrationRule {
public bool apply(PNLSettings settings, out PNLSettings new_settings) {
if (settings != null) {
if (_apply(ref settings, settings.animations, (s, t) => s.with(animations: t)) |
_apply(ref settings, settings.group_header_animations, (s, t) => s.with(group_header_animations: t)) |
_apply(ref settings, settings.header_row_animations, (s, t) => s.with(header_row_animations: t))) {
new_settings = settings;
return true;
}
}
new_settings = null;
return false;
}
bool _apply(ref PNLSettings settings, IEnumerable<PNLAnimationTriggerData> triggers, Func<PNLSettings, IEnumerable<PNLAnimationTriggerData>, PNLSettings> set_triggers) {
if (_apply(triggers, out var new_triggers)) {
settings = set_triggers(settings, new_triggers);
return true;
}
return false;
}
bool _apply(IEnumerable<PNLAnimationTriggerData> triggers, out IEnumerable<PNLAnimationTriggerData> result) {
var trigger_map = triggers.ToImmutableDictionary(t => t.expression);
var snapshot = trigger_map;
foreach (var t in trigger_map.Values) {
var anims = t.animations.ToList();
int orig_count = anims.Count;
anims.RemoveWhere(a => !Enum.IsDefined(typeof(PNLView.Columns), a.column));
if (anims.Count < orig_count)
trigger_map = trigger_map.SetItem(t.expression, t.with(animations: anims.ToArray()));
}
if (trigger_map != snapshot) {
result = trigger_map.Values;
return true;
}
result = null;
return false;
}
}
[Export(typeof(IPNLSettingsMigrationRule))]
class EnableGridBackgroundColorMigrationRule : IPNLSettingsMigrationRule {
public bool apply(PNLSettings settings, out PNLSettings new_settings) {
if (settings != null && !settings.grid.enable_background_color) {
new_settings = settings.with(grid: settings.grid.with(enable_background_color: true));
return true;
}
new_settings = null;
return false;
}
}
}
using bts.api;
using bts.collections;
using bts.settings;
using bts.shell.pnl;
using bts.spark;
using bts.utils;
using mbts;
using mbts.config;
using mbts.persist;
using mbts.spark;
using mbts.ui;
using mbts.ui.controls;
using mbts.ui.settings;
using mbts.ui.spark;
using mbts.ui.utility;
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Collections.Specialized;
using System.ComponentModel;
using System.ComponentModel.Composition;
using System.Linq;
using System.Reactive.Linq;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Media;
namespace bts.shell {
class PNLWindowFactory : ShellWindowFactory<PNLSaveState> {
const string _window_type = "PNL", _window_title = "PNL";
readonly IBus _bus;
readonly OrdersModel _orders_model;
readonly UserSettingsHub _settings_hub;
readonly PNLConfig _pnl_conf;
readonly IShellWindowThreadIDProvider _thread_id_provider;
public PNLWindowFactory(IShellModule owner, IBus bus, UserSettingsHub settings_hub, OrdersModel orders_model) : base(owner, _window_type) {
_bus = bus;
_orders_model = orders_model;
_settings_hub = settings_hub;
_pnl_conf = new PNLConfig(Configuration.get<bts.config.PNLOptions>());
_thread_id_provider = ShellWindowThreadIDFactory.create(_window_type, Configuration.get<bts.config.MultiThreadedWindows>().pnl);
_dispose = bus.subscribe(SparkTags.ShowPNLTag).OnDispatcher().Subscribe(_show);
}
public override Task<IShellWindow> load(PNLSaveState state) =>
Task.FromResult(_create(state.settings));
void _show(PNLSettings settings) =>
_bus.post(SparkTags.ShowWindow, _create(settings));
IShellWindow _create(PNLSettings settings) {
var settings_manager = _settings_hub.create_pnl_settings_manager(settings);
// Need to keep a local copy of the columns, since they are defined in xaml (not the viewmodel), and get_save_state may be called on a non-dispatcher thread.
// If that happens, we would be unable to access _grid.Columns, and thus unable to get the current column settings.
// Use an ImmutableList to avoid any threading issues when responding to CollectionChanged events on the dispatcher thread.
ImmutableList<DIYGrid.ColumnBase> cached_columns = ImmutableList<DIYGrid.ColumnBase>.Empty;
SparkWindow<PNLSaveState> main_win = null;
main_win = SparkWindow<PNLSaveState>.create(_window_type, _window_title, _bus, this, Icons.Accounting, _thread_id_provider.next_id(), () => {
var vm = new PNLVM(_bus, _orders_model, settings ?? PNLSettings.Default);
var content = new PNLView {
DataContext = vm
};
vm.sorting_changed += sh => {
try {
DIYGridSortHelper.set_sort_directions(sh, content._grid.Columns);
} catch { }
};
vm.SubscribeDisposed(
DIYGridSortHelper.observe_active_comparers<PNLView.Columns, PNLRowVM>(content._grid, PNLVM.get_column_comparer)
.OnDispatcher()
.Subscribe(sh => vm.sorting_hierarchy = sh.Select(s => (s.key, s.direction)).ToList()));
cached_columns = ImmutableList<DIYGrid.ColumnBase>.Empty.AddRange(content._grid.Columns);
Func<PNLSaveState> get_save_state = () => {
var cols = cached_columns.ToArray(c => c.create());
var filters = vm.get_filters_states();
var sorting_criterion = vm.sorting_hierarchy.Select(tup => new SortingCriteria(tup.key.ToString(), tup.direction == ListSortDirection.Ascending)).ToArray();
var calc_type = vm.calculation_type;
var state = settings_manager.instance_settings;
if (state == null) {
// don't use defaults if the user manipulated the window state outside of the settings dialog (e.g., resized column(s) via drag, or changed sort/filter)
var defaults = settings_manager.default_settings;
if (calc_type == defaults.calculation_type &&
defaults.sorting_criterion.SequenceEqual(sorting_criterion) &&
defaults.grid.columns.SequenceEqual(cols) && defaults.filters.safe_equals(filters)) {
// the user's current state still matches the defaults (i.e., no outside manipulation was performed); return null so this window will use the latest default settings when restored
return new PNLSaveState { use_defaults = true, settings = null };
}
state = defaults;
}
// assign latest values, as they may have be modified outside of the settings dialog (in which case the settings manager would be out of date)
state = state.with(
sorting_criterion,
vm.anchors,
filters,
state.grid.with(columns: cols, background: vm.grid_background_color.rgb_color()),
calc_type,
vm.header_row_display);
return new PNLSaveState { use_defaults = false, settings = state };
};
IEnumerable<SparkCommand> commands = new[] {
new SparkCommandCommand("Collapse All", Icons.RightArrow, new DelegateCommand(() => vm.set_all_expanded(false))),
new SparkCommandCommand("Expand All", Icons.DownArrow, new DelegateCommand(() => vm.set_all_expanded(true))),
new SparkCommandCommand("Play", Icons.Play, new DelegateCommand(() => vm.is_paused = false)) {
show_in_context_menu = _pnl_conf.show_pause_resume,
show_in_toolbar = _pnl_conf.show_pause_resume
},
new SparkCommandCommand("Pause", Icons.Pause, new DelegateCommand(() => vm.is_paused = true)) {
show_in_context_menu = _pnl_conf.show_pause_resume,
show_in_toolbar = _pnl_conf.show_pause_resume
},
SparkCommands.window_settings_command(new DialogCommand(() => {
settings_manager.instance_settings = get_save_state().settings;
var dialog_vm = new ShellSettingsDialogVM<PNLSettings, PNLSettingsVM>(
settings_manager,
us => us.pnl,
UserSettings.FileExtensions.PNL,
s => new PNLSettingsVM(s));
var dialog = new ShellSettingsDialog { DataContext = dialog_vm };
if (main_win != null)
dialog_vm.SubscribeDisposed(main_win.literal_title_source.OnDispatcher().Subscribe(t => dialog_vm.title = t));
dialog_vm.RequestClose += on_request_close;
return dialog;
void on_request_close(object sender, EventArgs e) =>
WindowUtilities.force_close(dialog);
}))
};
IDisposable dispose = Disposable.Create(
settings_manager.subscribe_current().OnDispatcher().Subscribe(s => {
vm.load_from(s);
var col_setting_map = s.grid.columns.ToDictionary(c => c.id);
var fallback_family = new FontFamily(s.grid.font_family);
// group
var group_col_setting_map = s.group_columns.ToDictionary(c => c.id);
var group_appearance = s.group_cell_appearance.to_appearance();
// header
var header_col_setting_map = s.header_columns.ToDictionary(c => c.id);
var header_appearance = s.header_cell_appearance.to_appearance();
//
foreach (var c in content._grid.Columns.OfType<PNLColumn>()) {
if (col_setting_map.TryGetValue(c.id, out var cs)) {
c.load_from(cs);
}
if (group_col_setting_map.TryGetValue(c.id, out var gcs)) {
c.load_group_params(
group_appearance.font_size,
gcs.font_weight_override ?? group_appearance.font_weight,
gcs.font_color_override ?? group_appearance.foreground,
group_appearance.font_family);
}
if (header_col_setting_map.TryGetValue(c.id, out var hcs)) {
c.load_header_params(
header_appearance.font_size,
hcs.font_weight_override ?? header_appearance.font_weight,
hcs.font_color_override ?? header_appearance.foreground,
header_appearance.font_family);
}
}
CollectionHelper.sync_with_never_duplicate(content._grid.Columns, s.grid.columns, cs => {
throw new InvalidOperationException();
}, c => c.id.ToLower(), cs => cs.id.ToLower());
}),
content._grid.Columns.Subscribe((s, e) => {
switch (e.Action) {
case NotifyCollectionChangedAction.Add: add_new(); break;
case NotifyCollectionChangedAction.Remove: remove_old(); break;
case NotifyCollectionChangedAction.Replace:
case NotifyCollectionChangedAction.Move:
remove_old();
add_new();
break;
case NotifyCollectionChangedAction.Reset:
cached_columns = ImmutableList<DIYGrid.ColumnBase>.Empty.AddRange(content._grid.Columns);
break;
}
void add_new() =>
cached_columns = e.NewStartingIndex >= 0 ?
cached_columns.InsertRange(e.NewStartingIndex, e.NewItems.OfType<DIYGrid.ColumnBase>()) :
cached_columns.AddRange(e.NewItems.OfType<DIYGrid.ColumnBase>());
void remove_old() =>
cached_columns = e.OldStartingIndex >= 0 ?
cached_columns.RemoveRange(e.OldStartingIndex, e.OldItems.Count) :
cached_columns.RemoveRange(e.OldItems.OfType<DIYGrid.ColumnBase>());
}),
Disposable.wrap_collection(commands.OfType<IDisposable>()),
vm);
return (content, commands, get_save_state, dispose);
},
min_width_offset: SystemParameters.HorizontalScrollBarButtonWidth + 8,
min_height_offset: 60);
return main_win;
}
readonly struct PNLConfig {
public readonly bool
enable_animations,
show_pause_resume;
public PNLConfig(bts.config.PNLOptions options) {
enable_animations = options.enable_animations;
show_pause_resume = options.show_pause_resume;
}
}
[Export(typeof(IPNLSettingsMigrationRule))]
class AddMissingAnimationsMigrationRule : IPNLSettingsMigrationRule {
public bool apply(PNLSettings settings, out PNLSettings new_settings) {
if (settings != null && (
_apply(ref settings, settings.animations, PNLSettings.default_animations, (s, anims) => s.with(animations: anims)) |
_apply(ref settings, settings.group_header_animations, PNLSettings.default_group_header_animations, (s, anims) => s.with(group_header_animations: anims)) |
_apply(ref settings, settings.header_row_animations, PNLSettings.default_header_row_animations, (s, anims) => s.with(header_row_animations: anims)))) {
//
new_settings = settings;
return true;
}
new_settings = null;
return false;
}
static bool _apply(ref PNLSettings settings, IEnumerable<PNLAnimationTriggerData> actual_triggers, IEnumerable<PNLAnimationTriggerData> default_triggers, Func<PNLSettings, PNLAnimationTriggerData[], PNLSettings> set_triggers) {
if (_apply(actual_triggers, default_triggers, out var result)) {
settings = set_triggers(settings, actual_triggers.Concat(result).ToArray());
return true;
}
return false;
}
static bool _apply(IEnumerable<PNLAnimationTriggerData> actual_triggers, IEnumerable<PNLAnimationTriggerData> default_triggers, out IEnumerable<PNLAnimationTriggerData> result) {
var default_trigger_map = default_triggers.ToDictionary(t => t.expression);
var actual_trigger_map = actual_triggers.ToImmutableDictionary(t => t.expression);
var snapshot = actual_trigger_map;
foreach (var kv in snapshot) {
if (default_trigger_map.TryGetValue(kv.Key, out var default_anims)) {
// assume 1 animation per column
var actual_col_anims = kv.Value.animations.ToDictionaryCrushed(a => a.column);
var default_col_anims = default_anims.animations.ToDictionaryCrushed(a => a.column);
actual_col_anims.Difference(default_col_anims,
out var exclusive_actual,
out var exclusive_defaults,
out var diff);
var new_col_anims = exclusive_defaults.ToList();
if (new_col_anims.Count > 0) {
// add the original col animations to the new ones (combine them)
new_col_anims.AddRange(actual_col_anims);
actual_trigger_map = actual_trigger_map.SetItem(kv.Key, kv.Value.with(animations: new_col_anims.Select(col_anim => col_anim.Value).ToArray()));
}
}
}
foreach (var kv in default_trigger_map) {
if (!actual_trigger_map.ContainsKey(kv.Key))
actual_trigger_map = actual_trigger_map.Add(kv.Key, kv.Value);
}
if (actual_trigger_map != snapshot) {
result = actual_trigger_map.Values;
return true;
}
result = null;
return false;
}
}
[Export(typeof(IPNLSettingsMigrationRule))]
class AddMissingColumnsMigrationRule : IPNLSettingsMigrationRule {
public bool apply(PNLSettings settings, out PNLSettings new_settings) {
if (settings != null) {
var defaults = PNLSettings.Default;
if (_apply(ref settings, settings.grid.columns, defaults.grid.columns, (s, cols) => s.with(grid: s.grid.with(cols))) |
_apply(ref settings, settings.group_columns, defaults.group_columns, (s, cols) => s.with(group_columns: cols)) |
_apply(ref settings, settings.header_columns, defaults.header_columns, (s, cols) => s.with(header_columns: cols))) {
new_settings = settings;
return true;
}
}
new_settings = null;
return false;
}
static bool _apply(ref PNLSettings settings, IEnumerable<GridColumnSettings> actual_columns, IEnumerable<GridColumnSettings> default_columns, Func<PNLSettings, IEnumerable<GridColumnSettings>, PNLSettings> set_columns) {
if (GridSettingsHelper.add_missing_columns(actual_columns, default_columns, out var new_cols)) {
settings = set_columns(settings, new_cols);
return true;
}
return false;
}
}
[Export(typeof(IPNLSettingsMigrationRule))]
class RemoveColumnsMigrationRule : IPNLSettingsMigrationRule {
public bool apply(PNLSettings settings, out PNLSettings new_settings) {
if (settings != null) {
if (_apply(ref settings, settings.grid.columns, (s, cols) => s.with(grid: s.grid.with(cols))) |
_apply(ref settings, settings.group_columns, (s, cols) => s.with(group_columns: cols)) |
_apply(ref settings, settings.header_columns, (s, cols) => s.with(header_columns: cols))) {
new_settings = settings;
return true;
}
}
new_settings = null;
return false;
}
bool _apply(ref PNLSettings settings, IEnumerable<GridColumnSettings> columns, Func<PNLSettings, IEnumerable<GridColumnSettings>, PNLSettings> set_columns) {
if (_apply(columns, out var new_cols)) {
settings = set_columns(settings, new_cols);
return true;
}
return false;
}
bool _apply(IEnumerable<GridColumnSettings> columns, out IEnumerable<GridColumnSettings> result) {
var col_list = columns.ToList();
int orig_count = col_list.Count;
col_list.RemoveWhere(c => !Enum.TryParse<PNLView.Columns>(c.id, true, out var _));
if (col_list.Count < orig_count) {
result = col_list;
return true;
}
result = null;
return false;
}
}
[Export(typeof(IPNLSettingsMigrationRule))]
class RemoveColumnAnimationsMigrationRule : IPNLSettingsMigrationRule {
public bool apply(PNLSettings settings, out PNLSettings new_settings) {
if (settings != null) {
if (_apply(ref settings, settings.animations, (s, t) => s.with(animations: t)) |
_apply(ref settings, settings.group_header_animations, (s, t) => s.with(group_header_animations: t)) |
_apply(ref settings, settings.header_row_animations, (s, t) => s.with(header_row_animations: t))) {
new_settings = settings;
return true;
}
}
new_settings = null;
return false;
}
bool _apply(ref PNLSettings settings, IEnumerable<PNLAnimationTriggerData> triggers, Func<PNLSettings, IEnumerable<PNLAnimationTriggerData>, PNLSettings> set_triggers) {
if (_apply(triggers, out var new_triggers)) {
settings = set_triggers(settings, new_triggers);
return true;
}
return false;
}
bool _apply(IEnumerable<PNLAnimationTriggerData> triggers, out IEnumerable<PNLAnimationTriggerData> result) {
var trigger_map = triggers.ToImmutableDictionary(t => t.expression);
var snapshot = trigger_map;
foreach (var t in trigger_map.Values) {
var anims = t.animations.ToList();
int orig_count = anims.Count;
anims.RemoveWhere(a => !Enum.IsDefined(typeof(PNLView.Columns), a.column));
if (anims.Count < orig_count)
trigger_map = trigger_map.SetItem(t.expression, t.with(animations: anims.ToArray()));
}
if (trigger_map != snapshot) {
result = trigger_map.Values;
return true;
}
result = null;
return false;
}
}
[Export(typeof(IPNLSettingsMigrationRule))]
class EnableGridBackgroundColorMigrationRule : IPNLSettingsMigrationRule {
public bool apply(PNLSettings settings, out PNLSettings new_settings) {
if (settings != null && !settings.grid.enable_background_color) {
new_settings = settings.with(grid: settings.grid.with(enable_background_color: true));
return true;
}
new_settings = null;
return false;
}
}
}
}
@@ -0,0 +1,8 @@
using mbts.ui.viewmodels;
namespace bts.shell.pnl {
class PNLAnchorHierarchyVM : ItemOrderParameterVM<PNLView.Columns> {
public PNLAnchorHierarchyVM()
: base("", PNLSettings.anchorable_columns) { }
}
}
@@ -1,122 +1,88 @@
using bts.proto;
using bts.collections;
using bts.proto;
using mbts;
using mbts.spark;
using mbts.ui;
using mbts.ui.controls;
using mbts.ui.settings;
using mbts.ui.shell;
using mbts.ui.utility.text;
using mbts.ui.viewmodels;
using System;
using System.Windows;
using System.Windows.Media;
using FontFamily = System.Windows.Media.FontFamily;
namespace bts.shell.pnl {
class PNLCellAppearanceOverrideVM : ViewModelBase {
bool
_is_background_overridden,
_is_font_color_overridden,
_is_font_weight_overridden,
_is_font_family_overridden,
_is_font_size_overridden;
class PNLCellAppearanceOverrideVM : ParameterGroupVM {
public PNLCellAppearanceOverrideVM(string display_name, string rule = null) : base(display_name) {
parameters.AddRange(background, font_color, font_weight);
background.can_override = true;
font_color.can_override = true;
font_weight.can_override = true;
}
FontWeight _font_weight_override;
FontFamily _font_family_override;
double _font_size_override = double.NaN;
// background
public bool is_background_overridden { get => _is_background_overridden; set => Set(ref _is_background_overridden, value); }
public ColorPickerPopupVM background_override { get; } = new ColorPickerPopupVM {
bind_opacity_to_alpha = true,
opacity = 1
};
// font color
public bool is_font_color_overridden { get => _is_font_color_overridden; set => Set(ref _is_font_color_overridden, value); }
public ColorPickerPopupVM font_color_override { get; } = new ColorPickerPopupVM();
// font weight
public bool is_font_weight_overridden { get => _is_font_weight_overridden; set => Set(ref _is_font_weight_overridden, value); }
public FontWeight font_weight_override { get => _font_weight_override; set => Set(ref _font_weight_override, value); }
// font family
public bool is_font_family_overriden { get => _is_font_family_overridden; set => Set(ref _is_font_family_overridden, value); }
public FontFamily font_family_override { get => _font_family_override; set => Set(ref _font_family_override, value); }
// font size
public bool is_font_size_overriden { get => _is_font_size_overridden; set => Set(ref _is_font_size_overridden, value); }
public double font_size_override { get => _font_size_override; set => Set(ref _font_size_override, value); }
public ColorOverrideParameterVM background { get; } = new ColorOverrideParameterVM("Background");
public ColorOverrideParameterVM font_color { get; } = new ColorOverrideParameterVM("Font Color");
public FontWeightOverrideParameterVM font_weight { get; } = new FontWeightOverrideParameterVM("Font Weight");
public GridColumnSettings apply_to(GridColumnSettings target) =>
target.with(
font_weight_override: new NullableFontWeightProto(is_font_weight_overridden ? font_weight_override : (FontWeight?)null),
font_color_override: new NullableColorProto(is_font_color_overridden ? font_color_override.color : (Color?)null),
background_color_override: new NullableColorProto(is_background_overridden ? background_override.color : (Color?)null));
font_weight_override: new NullableFontWeightProto(font_weight.effective_is_overridden ? font_weight.override_value : (FontWeight?)null),
font_color_override: new NullableColorProto(font_color.effective_is_overridden ? font_color.override_value : (Color?)null),
background_color_override: new NullableColorProto(background.effective_is_overridden ? background.override_value : (Color?)null));
public void load(GridColumnSettings settings) {
if (settings != null)
load(settings.font_weight_override, settings.font_color_override, settings.background_color_override);
public IDisposable connect_default_values(GridSettingsVM grid_defaults) =>
Disposable.Create(
grid_defaults.subscribe_font().Subscribe(v => {
font_weight.family = v.family;
font_weight.default_value = v.weight;
font_weight.size = v.size;
}),
grid_defaults.subscribe_font_color().Subscribe(v => {
font_color.default_value = v;
}),
grid_defaults.subscribe_background().Subscribe(v => {
background.default_value = v;
}));
public PNLAnimation create(PNLView.Columns col_key) {
var rv = new PNLAnimation {
column = col_key
};
if (background.effective_is_overridden)
rv.background = background.override_value.rgb_color();
if (font_color.effective_is_overridden)
rv.foreground = font_color.override_value.rgb_color();
if (font_weight.effective_is_overridden)
rv.font_weight = font_weight.override_value.ToOpenTypeWeight();
return rv;
}
public void load(FontParamsProto font_params) {
if (font_params == null)
return;
public void load(GridColumnSettings value) {
// font weight
is_font_weight_overridden = font_params.font_weight > 0;
if (is_font_weight_overridden)
font_weight_override = FontWeight.FromOpenTypeWeight(font_params.font_weight);
font_weight.is_overridden = value.font_weight_override.HasValue;
if (font_weight.is_overridden)
font_weight.override_value = value.font_weight_override.Value;
// font color
is_font_color_overridden = font_params.font_color > 0;
if (is_font_color_overridden)
font_color_override.color = font_params.font_color.rgb_color();
// font family
if (!string.IsNullOrEmpty(font_params.font_family)) {
font_family_override = new FontFamily(font_params.font_family);
}
if (!double.IsNaN(font_params.font_size) && font_params.font_size > 0) {
font_size_override = font_params.font_size;
} else {
font_size_override = double.NaN;
}
}
public void load(PNLAnimation animation) {
if (animation != null)
load(
animation.font_weight.HasValue ? FontWeight.FromOpenTypeWeight(animation.font_weight.Value) : (FontWeight?)null,
animation.foreground?.argb_color(),
animation.background?.argb_color());
}
public void load(FontWeight? font_weight, Color? font_color, Color? background) {
// font weight
is_font_weight_overridden = font_weight.HasValue;
if (is_font_weight_overridden)
font_weight_override = font_weight.Value;
// font color
is_font_color_overridden = font_color.HasValue;
if (is_font_color_overridden)
font_color_override.color = font_color.Value;
font_color.is_overridden = value.font_color_override.HasValue;
if (font_color.is_overridden)
font_color.override_value = value.font_color_override.Value;
// background
is_background_overridden = background.HasValue;
if (background.HasValue)
background_override.color = background.Value;
background.is_overridden = value.background_color_override.HasValue;
if (background.is_overridden)
background.override_value = value.background_color_override.Value;
}
public PNLAnimation to_animation(PNLView.Columns key) =>
new PNLAnimation {
column = key,
foreground = is_font_color_overridden ? font_color_override.color.rgb_color() : (uint?)null,
background = is_background_overridden ? background_override.color.rgb_color() : (uint?)null,
font_weight = is_font_weight_overridden ? font_weight_override.ToOpenTypeWeight() : (int?)null
};
public PNLCellAppearanceProto to_appearance_proto() =>
new PNLCellAppearanceProto {
background = is_background_overridden ? background_override.color.rgb_color() : (uint?)null,
font_params = to_font_params()
};
public FontParamsProto to_font_params() =>
new FontParamsProto {
typeface = new TypefaceProto(
font_family_override?.name(),
is_font_weight_overridden ? font_weight_override.ToOpenTypeWeight() : 0),
font_color = is_font_color_overridden ? font_color_override.color.rgb_color() : 0,
font_size = font_size_override
};
public void load(PNLAnimation value) {
// font weight
font_weight.is_overridden = value.font_weight.HasValue && value.font_weight.Value > 0;
if (font_weight.is_overridden)
font_weight.override_value = FontWeight.FromOpenTypeWeight(value.font_weight.Value);
// font color
font_color.is_overridden = value.foreground.HasValue;
if (font_color.is_overridden)
font_color.override_value = value.foreground.Value.argb_color();
// background
background.is_overridden = value.background.HasValue;
if (background.is_overridden)
background.override_value = value.background.Value.argb_color();
}
}
}
@@ -1,34 +1,51 @@
using mbts.ui.controls;
using mbts.ui.shell;
using System.Windows;
using FontFamily = System.Windows.Media.FontFamily;
using bts.collections;
using mbts.ui.viewmodels;
namespace bts.shell.pnl {
class PNLCellAppearanceVM : ViewModelBase {
FontWeight _font_weight;
FontFamily _font_family;
double _font_size = double.NaN;
class PNLHeaderRowCellAppearanceVM : ParameterGroupVM {
readonly FontParameterVM _font = new FontParameterVM("Font");
readonly ColorParameterVM
_background = new ColorParameterVM("Background"),
_font_color = new ColorParameterVM("Font Color");
readonly BooleanParameterVM
_show_totals = new BooleanParameterVM("Show Totals"),
_show_selected_totals = new BooleanParameterVM("Show Selected Totals");
public ColorPickerPopupVM background { get; } = new ColorPickerPopupVM { bind_opacity_to_alpha = true, opacity = 1 };
public ColorPickerPopupVM font_color { get; } = new ColorPickerPopupVM();
public FontWeight font_weight { get => _font_weight; set => Set(ref _font_weight, value); }
public FontFamily font_family { get => _font_family; set => Set(ref _font_family, value); }
public double font_size { get => _font_size; set => Set(ref _font_size, value); }
public PNLHeaderRowCellAppearanceVM() : base("Header Row Display") {
parameters.AddRange(
HeaderedParameterGroupVM.create("Fonts/Colors", _font, _font_color, _background),
HeaderedParameterGroupVM.create("Display", _show_totals, _show_selected_totals));
}
public PNLHeaderRowDisplay display {
get {
var rv = PNLHeaderRowDisplay.None;
if (_show_totals.value)
rv |= PNLHeaderRowDisplay.Total;
if (_show_selected_totals.value)
rv |= PNLHeaderRowDisplay.SelectedTotal;
return rv;
}
set {
_show_totals.value = (display & PNLHeaderRowDisplay.Total) != 0;
_show_selected_totals.value = (display & PNLHeaderRowDisplay.SelectedTotal) != 0;
}
}
public PNLCellAppearance to_appearance() =>
new PNLCellAppearance(font_color.color, background.color, font_weight, font_family, font_size);
new PNLCellAppearance(_font_color.value, _background.value, _font.weight, _font.family, _font.size);
public void load(PNLCellAppearance appearance) {
if (appearance.background.HasValue)
background.color = appearance.background.Value;
_background.value = appearance.background.Value;
if (appearance.foreground.HasValue)
font_color.color = appearance.foreground.Value;
_font_color.value = appearance.foreground.Value;
if (appearance.font_family != null)
font_family = appearance.font_family;
_font.family = appearance.font_family;
if (appearance.font_weight.HasValue)
font_weight = appearance.font_weight.Value;
_font.weight = appearance.font_weight.Value;
if (appearance.font_size > 0)
font_size = appearance.font_size;
_font.size = appearance.font_size;
}
}
}
@@ -1,133 +1,216 @@
using bts.collections;
using bts.utils;
using mbts;
using mbts.spark;
using mbts.ui.controls;
using mbts.ui.settings;
using mbts.ui.shell;
using mbts.ui.utilities;
using mbts.ui.utility;
using mbts.ui.viewmodels;
using mbts.utils;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Collections.Specialized;
using System.Linq;
using System.Reactive.Linq;
using System.Windows;
namespace bts.shell.pnl {
class PNLColumnVM : ViewModelBase, ISimpleColumnVM {
readonly SimpleGridSettingsVM _owner;
readonly PNLView.Columns _pnl_id;
object _selected_item;
string _display_name;
bool _is_visible;
double _width;
HorizontalAlignment _horizontal_alignment;
class PNLModularCellAppearanceVM : TabularParameterGroupVM {
public static PNLModularCellAppearanceVM create(PNLColumnCellAppearanceVM default_cell_appearance, IReadOnlyObservableCollection<object> rules) {
var composite = new ObservableAggregateCollection<object>(new ObservableCollection<object> { default_cell_appearance });
composite.add_collection(rules);
return new PNLModularCellAppearanceVM(composite) {
selected_item = default_cell_appearance
};
}
public PNLColumnVM(SimpleGridSettingsVM owner, PNLView.Columns id) {
_owner = owner;
_pnl_id = id;
default_column_vm.PropertyChanged += echo;
void echo(object sender, PropertyChangedEventArgs e) {
switch (e.PropertyName) {
case nameof(is_font_weight_overridden):
case nameof(is_font_color_overridden):
RaisePropertyChanged(e.PropertyName);
PNLModularCellAppearanceVM(ObservableCollection<object> parameters) : base("", parameters) { }
}
class PNLColumnCellAppearanceVM : ParameterGroupVM {
PNLCellAppearanceOverrideVM _selected_cell_mode;
public static Rule create_rule(string rule) =>
new Rule(rule);
public PNLColumnCellAppearanceVM(string display_name) : base(display_name) {
parameters.AddRange(default_vm, group_vm, header_vm);
_selected_cell_mode = default_vm;
}
public PNLCellAppearanceOverrideVM selected_cell_mode { get => _selected_cell_mode; set => Set(ref _selected_cell_mode, value); }
public PNLCellAppearanceOverrideVM default_vm { get; } = new PNLCellAppearanceOverrideVM("Row") { is_expanded = true };
public PNLCellAppearanceOverrideVM group_vm { get; } = new PNLCellAppearanceOverrideVM("Group Row");
public PNLCellAppearanceOverrideVM header_vm { get; } = new PNLCellAppearanceOverrideVM("Header Row");
IEnumerable<PNLCellAppearanceOverrideVM> _cell_modes {
get {
yield return default_vm;
yield return group_vm;
yield return header_vm;
}
}
public IDisposable connect_default_values(GridSettingsVM grid_defaults) =>
new System.Reactive.Disposables.CompositeDisposable(
_cell_modes.Select(c => c.connect_default_values(grid_defaults)));
public class Rule : PNLColumnCellAppearanceVM {
public Rule(string rule_text) : base($"When {DisplayNameHelper.expression_snake_to_title_case(rule_text)}") =>
this.rule_text = rule_text;
public string rule_text { get; }
}
}
class PNLColumnSettingsVM : GridColumnSettingsVM {
readonly ObservableAggregateCollection<object> _cell_appearance_groups = new ObservableAggregateCollection<object>();
readonly _AutoRuleTabsWrapperVM _fonts_and_colors;
readonly PNLView.Columns _key;
public PNLColumnSettingsVM(PNLView.Columns key) : base(null) {
_key = key;
_fonts_and_colors = new _AutoRuleTabsWrapperVM("Fonts/Colors", key);
column_layout = new HeaderedParameterGroupVM("Column Layout", new ObservableCollection<object> {
header, width, horizontal_alignment
});
apply_flags(GridColumnSettingsFeatureFlags.None);
}
public HeaderedParameterGroupVM column_layout { get; }
public HeaderedParameterGroupVM fonts_and_colors => _fonts_and_colors;
public PNLColumnCellAppearanceVM default_cell_appearance => _fonts_and_colors.cell_appearance;
IEnumerable<PNLCellAppearanceOverrideVM> _all_cell_modes =>
_fonts_and_colors.parameters
.OfType<PNLColumnCellAppearanceVM>()
.SelectMany(c => c.parameters.OfType<PNLCellAppearanceOverrideVM>());
public void load_from(GridColumnSettings value, IReadOnlyDictionary<string, PNLAnimation> triggers, GridColumnSettings group, IReadOnlyDictionary<string, PNLAnimation> group_triggers, IReadOnlyDictionary<string, PNLAnimation> header_triggers, GridColumnSettings header) {
load_from(value);
_fonts_and_colors.load_from(value, triggers, group, group_triggers, header_triggers, header);
}
public IEnumerable<(string expression, PNLAnimation row, PNLAnimation group, PNLAnimation header)> get_triggers() =>
_fonts_and_colors.create();
public IDisposable connect_default_values(GridSettingsVM grid_defaults) =>
_fonts_and_colors.connect_default_values(grid_defaults);
public override string ToString() => "Default";
public override void sync_params() =>
parameters.SetItems(column_layout, fonts_and_colors);
/// <summary>
/// This class stores a <see cref="PNLColumnCellAppearanceVM"/> instance in a collection of parameters, and automatically replaces it with a <see cref="PNLModularCellAppearanceVM"/> when rules are loaded.
/// Conversely, it replaces the <see cref="PNLModularCellAppearanceVM"/> with the original instance when rules are unloaded.
/// The impetus: A special 'TabControl' based DataTemplate is defined in PNLSettingsView.xaml as the default for content of type <see cref="PNLModularCellAppearanceVM"/>.
/// </summary>
class _AutoRuleTabsWrapperVM : HeaderedParameterGroupVM {
public readonly PNLColumnCellAppearanceVM cell_appearance;
readonly ObservableAggregateCollection<object> _composite_parameters;
readonly ObservableCollection<PNLColumnCellAppearanceVM.Rule> _rules = new ObservableCollection<PNLColumnCellAppearanceVM.Rule>();
readonly IReadOnlyObservableCollection<object> _wrapped_rules;
readonly DeferrableActionWrapper _sync_params;
PNLModularCellAppearanceVM _cell_appearance_rules;
PNLView.Columns _id;
public _AutoRuleTabsWrapperVM(string display_name, PNLView.Columns id)
: this(display_name, id, new ObservableAggregateCollection<object>()) { }
_AutoRuleTabsWrapperVM(string display_name, PNLView.Columns id, ObservableAggregateCollection<object> parameters) : base(display_name, parameters) {
_id = id;
_composite_parameters = parameters;
cell_appearance = new PNLColumnCellAppearanceVM("Default");
_wrapped_rules = _rules.CastObservable<PNLColumnCellAppearanceVM.Rule, object>();
//_wrapped_rules = _rules.SelectObservable<PNLColumnCellAppearanceVM, object>(_ => _, obj => (PNLColumnCellAppearanceVM)obj);
// If there are no rules present, the view should display a flat list of font/colors parameters.
// If there are rules present, the view should use TabItems to represent each rule its associated font/color parameters,
// as well as an additional Tab for the 'defaults.'
// It is the responsibility of this class, as the viewmodel, to organize the parameters appropriately.
// Listen to rules.CollectionChanged events, and toggle between the 'flat list of default parameters' and the TabItem groups when rules.Count > 0 changes.
_composite_parameters.add_collection(cell_appearance.parameters);
_composite_parameters.add_collection(_wrapped_rules);
bool rules_present = false;
_sync_params = new DeferrableActionWrapper(sync_params);
_rules.CollectionChanged += (s, e) => _sync_params.execute();
void sync_params() {
if (rules_present != _rules.Count > 0) {
rules_present = !rules_present;
_composite_parameters.clear_collections();
if (rules_present) {
if (_cell_appearance_rules == null) {
_cell_appearance_rules = PNLModularCellAppearanceVM.create(cell_appearance, _wrapped_rules);
}
_composite_parameters.insert_collection(0, new ObservableCollection<object> { _cell_appearance_rules });
} else {
_composite_parameters.insert_collection(0, new ObservableCollection<object> { cell_appearance });
}
}
}
}
public IEnumerable<(string rule, PNLAnimation row, PNLAnimation group, PNLAnimation header)> create() =>
_rules.Select(r => (r.rule_text, r.default_vm.create(_id), r.group_vm.create(_id), r.header_vm.create(_id)));
public void load_from(GridColumnSettings value, IReadOnlyDictionary<string, PNLAnimation> triggers, GridColumnSettings group, IReadOnlyDictionary<string, PNLAnimation> group_triggers, IReadOnlyDictionary<string, PNLAnimation> header_triggers, GridColumnSettings header) {
using (_sync_params.defer_execution()) {
_rules.Clear();
cell_appearance.default_vm.load(value);
cell_appearance.group_vm.load(group);
cell_appearance.header_vm.load(header);
foreach (var kv in triggers) {
var rule_vm = PNLColumnCellAppearanceVM.create_rule(kv.Key);
rule_vm.default_vm.load(kv.Value);
if (group_triggers.TryGetValue(kv.Key, out var g)) {
rule_vm.group_vm.load(g);
}
if (header_triggers.TryGetValue(kv.Key, out var h))
rule_vm.header_vm.load(h);
_rules.Add(rule_vm);
}
}
}
GridSettingsVM _grid_defaults;
public IDisposable connect_default_values(GridSettingsVM grid_defaults) {
Log.assert(_grid_defaults == null && grid_defaults != null);
if (_rules.Count > 0) { }
_grid_defaults = grid_defaults;
_rules.CollectionChanged += _wtf;
_wtf(_rules, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
return Disposable.Create(
cell_appearance.connect_default_values(grid_defaults),
ObservableAutoWrapper.connect_item_hooks(_rules, r => r.connect_default_values(grid_defaults)),
ObservableAutoWrapper.connect_item_hooks(parameters,
obj => {
if (obj is PNLColumnCellAppearanceVM cell_app) {
return cell_app.connect_default_values(grid_defaults);
} else if (obj is PNLCellAppearanceOverrideVM cell_app_over) {
return cell_app_over.connect_default_values(grid_defaults);
}
return null;
}));
}
private void _wtf(object sender, NotifyCollectionChangedEventArgs e) {
if (_rules.Count > 0) {
}
switch (e.Action) {
case NotifyCollectionChangedAction.Reset:
break;
case NotifyCollectionChangedAction.Add:
break;
case NotifyCollectionChangedAction.Remove:
break;
default:
break;
}
}
}
public PNLCellAppearanceOverrideVM default_column_vm { get; } = new PNLCellAppearanceOverrideVM();
public PNLCellAppearanceOverrideVM group_column_vm { get; } = new PNLCellAppearanceOverrideVM();
public PNLCellAppearanceOverrideVM header_column_vm { get; } = new PNLCellAppearanceOverrideVM();
//
public ObservableCollection<PNLRuleColumnVM> rule_columns { get; } = new ObservableCollection<PNLRuleColumnVM>();
public object selected_item {
get => _selected_item;
set {
if (value != null)
Set(ref _selected_item, value);
}
}
public string id { get => _pnl_id.ToString(); set => Log.assert(value == _pnl_id.ToString()); }
public string display_name { get => _display_name; set => Set(ref _display_name, value); }
public double width { get => _width; set => Set(ref _width, value); }
public bool is_visible { get => _is_visible; set => Set(ref _is_visible, value); }
public bool is_font_color_overridden {
get => default_column_vm.is_font_color_overridden;
set => default_column_vm.is_font_color_overridden = value;
}
public HorizontalAlignment horizontal_alignment { get => _horizontal_alignment; set => Set(ref _horizontal_alignment, value); }
public ColorPickerPopupVM font_color_override => default_column_vm.font_color_override;
public bool is_font_weight_overridden { get => default_column_vm.is_font_weight_overridden; set => default_column_vm.is_font_weight_overridden = value; }
public FontWeight font_weight_override { get => default_column_vm.font_weight_override; set => default_column_vm.font_weight_override = value; }
public void apply_flags(GridColumnSettingsFeatureFlags flags) { }
public GridColumnSettings create() {
var result = new GridColumnSettings(id: id, display_name: display_name, is_visible: is_visible, horizontal_alignment: horizontal_alignment, width: width);
result = default_column_vm.apply_to(result);
return result;
}
public void load_from(GridColumnSettings value) {
_assert_id(value.id);
default_column_vm.load(value);
display_name = value.display_name;
width = value.width;
is_visible = value.is_visible;
horizontal_alignment = value.horizontal_alignment;
}
public void load_from(GridColumnSettings value, IReadOnlyDictionary<string, PNLAnimation> triggers, GridColumnSettings group, IReadOnlyDictionary<string, PNLAnimation> group_triggers, IReadOnlyDictionary<string, PNLAnimation> header_triggers, GridColumnSettings header) {
_assert_id(value.id);
load_from(value);
if (group != null)
group_column_vm.load(group);
if (header != null)
header_column_vm.load(header);
rule_columns.SetItems(triggers.Select(kv => {
var rule_col = new PNLRuleColumnVM(kv.Key);
rule_col.load_from(kv.Value, group_triggers.GetValueOrDefault(kv.Key), header_triggers.GetValueOrDefault(kv.Key));
return rule_col;
}));
}
public IEnumerable<(string expression, PNLAnimation row, PNLAnimation group, PNLAnimation header)> get_triggers() {
foreach (var col in rule_columns)
yield return (col.rule_text, col.row_params.to_animation(_pnl_id), col.group_params.to_animation(_pnl_id), col.header_params.to_animation(_pnl_id));
}
void _assert_id(string id) =>
Log.assert(id == _pnl_id.ToString(), $"PNLColumn id string '{id}' does not match the expected {typeof(PNLView.Columns)} key '{_pnl_id}.'");
public override string ToString() => "Default";
}
class PNLRuleColumnVM : ViewModelBase, IEquatable<PNLRuleColumnVM> {
public PNLRuleColumnVM(string rule_text) =>
this.rule_text = rule_text;
public string rule_text { get; }
public PNLCellAppearanceOverrideVM row_params { get; } = new PNLCellAppearanceOverrideVM();
public PNLCellAppearanceOverrideVM group_params { get; } = new PNLCellAppearanceOverrideVM();
public PNLCellAppearanceOverrideVM header_params { get; } = new PNLCellAppearanceOverrideVM();
public void load_from(PNLAnimation row, PNLAnimation group, PNLAnimation header) {
row_params.load(row);
group_params.load(group);
header_params.load(header);
}
public bool Equals(PNLRuleColumnVM other) =>
other != null && rule_text == other.rule_text;
public override bool Equals(object obj) => Equals(obj as PNLRuleColumnVM);
public override int GetHashCode() => rule_text.GetHashCode();
public override string ToString() => rule_text;
}
}
@@ -1,58 +1,60 @@
using bts.collections;
using mbts.spark;
using mbts.ui;
using mbts.ui.controls;
using mbts.ui.shell;
using bts.utils;
using mbts.ui.utility;
using mbts.ui.viewmodels;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Windows;
using System.Windows.Input;
using System.Linq;
using System.Windows.Media;
using FontFamily = System.Windows.Media.FontFamily;
namespace bts.shell.pnl {
class PNLGroupRowAppearanceVM : ViewModelBase, IValueFactory<(PNLCellAppearance appearance, IReadOnlyList<Color> background_colors)> {
FontWeight _font_weight;
FontFamily _font_family;
double _font_size = double.NaN;
class PNLGroupRowCellAppearanceVM : ParameterGroupVM {
readonly PNLAlternatingRowBackgroundVM _alternating_background_vm = new PNLAlternatingRowBackgroundVM();
readonly ColorParameterVM _font_color = new ColorParameterVM("Font Color");
readonly FontParameterVM _font = new FontParameterVM("Font");
public PNLGroupRowAppearanceVM() {
add_background_cmd = new DelegateCommand(_add_background);
// 4 elements is enough to alternate through any reasonable number of groups,
// and clicking to add new backgrounds seems awkward
for (int i = 0; i < 4; i++)
_add_background();
public PNLGroupRowCellAppearanceVM() : base("Group") {
parameters.AddRange(
new HeaderedParameterGroupVM("Fonts/Colors", new ObservableCollection<object> { _font, _font_color }),
_alternating_background_vm);
}
public ObservableCollection<NamedColorPickerPopupVM> background_colors { get; } = new ObservableCollection<NamedColorPickerPopupVM>();
public ColorPickerPopupVM font_color { get; } = new ColorPickerPopupVM();
public FontWeight font_weight { get => _font_weight; set => Set(ref _font_weight, value); }
public FontFamily font_family { get => _font_family; set => Set(ref _font_family, value); }
public double font_size { get => _font_size; set => Set(ref _font_size, value); }
public ICommand add_background_cmd { get; }
ObservableCollection<ColorParameterVM> _alternating_backgrounds => _alternating_background_vm.items;
public (PNLCellAppearance appearance, IReadOnlyList<Color> background_colors) create() =>
(new PNLCellAppearance(font_color.color, Colors.Transparent, font_weight, font_family, font_size),
background_colors.WrapSelectorRO(cp => cp.color));
public (PNLCellAppearance appearance, IReadOnlyList<Color> alternating_backgrounds) create() =>
(new PNLCellAppearance(_font_color.value, Colors.Transparent, _font.weight, _font.family, _font.size),
_alternating_backgrounds.Cast<ColorParameterVM>().Select(vm => vm.value).ToList());
public void load_from((PNLCellAppearance appearance, IReadOnlyList<Color> background_colors) value) {
var (app, bg_colors) = value;
// background colors
while (background_colors.Count < bg_colors.Count)
_add_background();
for (int i = 0; i < bg_colors.Count; i++)
background_colors[i].color = bg_colors[i];
//
if (app.foreground.HasValue) font_color.color = app.foreground.Value;
if (app.font_family != null) font_family = app.font_family;
if (app.font_weight.HasValue) font_weight = app.font_weight.Value;
if (app.font_size > 0) font_size = app.font_size;
public void load((PNLCellAppearance appearance, IReadOnlyList<Color> alternating_backgrounds) value) {
var (appearance, alternating_backgrounds) = value;
for (int i = 0; i < alternating_backgrounds.Count; i++) {
if (_alternating_backgrounds.Count > i) {
var vm = _alternating_backgrounds[i];
vm.value = alternating_backgrounds[i];
}
}
while (alternating_backgrounds.Count > _alternating_backgrounds.Count)
_alternating_backgrounds.Add(new ColorParameterVM($"Background {_alternating_backgrounds.Count}") {
value = alternating_backgrounds[_alternating_backgrounds.Count]
});
while (_alternating_backgrounds.Count > alternating_backgrounds.Count)
_alternating_backgrounds.RemoveAt(alternating_backgrounds.Count);
CollectionHelper.add_or_update_existing(_alternating_backgrounds, alternating_backgrounds, c => new ColorParameterVM("") { value = c }, (vm, c) => ((ColorParameterVM)vm).value = c);
if (appearance.foreground.HasValue)
_font_color.value = appearance.foreground.Value;
if (appearance.font_family != null)
_font.family = appearance.font_family;
if (appearance.font_weight.HasValue)
_font.weight = appearance.font_weight.Value;
if (appearance.font_size > 0)
_font.size = appearance.font_size;
}
}
void _add_background() =>
background_colors.Add(new NamedColorPickerPopupVM {
bind_opacity_to_alpha = true,
name = $"Background {background_colors.Count + 1}"
});
class PNLAlternatingRowBackgroundVM : HeaderedParameterGroupVM {
public PNLAlternatingRowBackgroundVM() : base("Alternating Backgrounds") =>
ObservableAutoWrapper.connect(items, parameters, c => c);
public ObservableCollection<ColorParameterVM> items { get; } = new ObservableCollection<ColorParameterVM>();
}
}
@@ -1,220 +1,145 @@
using bts.collections;
using bts.utils;
using mbts;
using mbts.collections;
using mbts.expression;
using mbts.spark;
using mbts.ui;
using mbts.ui.settings;
using mbts.ui.shell;
using mbts.ui.spark;
using mbts.ui.utility;
using mbts.utils;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.Dynamic;
using System.Linq;
using ColumnKeys = bts.shell.pnl.PNLView.Columns;
namespace bts.shell.pnl {
class PNLSettingsVM : DisposableViewModelBase, IWindowSettingsVM<PNLSettings>, IValueFactory<PNLSettings>, IObservable<PNLSettings>, ITemplateSelectorKey {
readonly IReadOnlyDictionary<ColumnKeys, AnchorItemVM> _anchor_cache;
readonly DeferrableActionWrapper _sync_anchors;
PNLCalculationType _calculation_type;
PNLHeaderRowDisplay _header_row_display;
PNLSettings _last_loaded;
public PNLSettingsVM(PNLSettings settings) {
// init anchor cache, deferrable resync
_anchor_cache = PNLSettings.anchorable_columns.ToDictionary(k => k, k => new AnchorItemVM(this, k));
_sync_anchors = new DeferrableActionWrapper(() => {
for (int i = 0; i < anchor_items.Count; i++) {
anchor_items[i].anchor_index = i + 1;
anchor_items[i].refresh();
}
});
core_settings_vm = new SimpleGridSettingsVM(column_factory: col_settings => {
var col_vm = new PNLColumnVM(core_settings_vm, EnumHelper.parse<ColumnKeys>(col_settings.id));
col_vm.load_from(col_settings);
return col_vm;
});
core_settings_vm.selection_highlight.bind_opacity_to_alpha = true;
_dispose = Disposable.Create(
anchor_items.Subscribe((s, e) => _sync_anchors.execute()));
load(settings);
}
public IEnumerable<AnchorItemVM> anchor_item_headers => _anchor_cache.Values;
public ObservableCollection<AnchorItemVM> anchor_items { get; } = new ObservableCollection<AnchorItemVM>();
public SimpleGridSettingsVM core_settings_vm { get; }
public PNLGroupRowAppearanceVM group_row_defaults { get; } = new PNLGroupRowAppearanceVM();
public PNLCellAppearanceVM header_row_defaults { get; } = new PNLCellAppearanceVM();
public PNLCalculationType calculation_type { get => _calculation_type; set => Set(ref _calculation_type, value); }
public IEnumerable<PNLCalculationType> calculation_types => EnumHelper.get_values_except(PNLCalculationType.None, PNLCalculationType.Same, PNLCalculationType.Opposite);
public PNLHeaderRowDisplay header_row_display { get => _header_row_display; set => Set(ref _header_row_display, value); }
public IEnumerable<PNLHeaderRowDisplay> header_row_display_options => PNLSettings.header_row_display_options;
string ITemplateSelectorKey.key => GridSettingsTemplateKeys.PNL;
public PNLSettings get_result() {
var s = _last_loaded == null ? new PNLSettings() : SparkUtils.clone_proto(_last_loaded);
s.calculation_type = _calculation_type;
s.grid = core_settings_vm.create();
using bts.collections;
using mbts;
using mbts.expression;
using mbts.spark;
using mbts.ui;
using mbts.ui.settings;
using mbts.ui.shell;
using mbts.ui.spark;
using mbts.ui.utility;
using mbts.utils;
using System;
using System.Collections.Generic;
using System.Linq;
using ColumnKeys = bts.shell.pnl.PNLView.Columns;
namespace bts.shell.pnl {
class PNLSettingsVM : DisposableViewModelBase, IWindowSettingsVM<PNLSettings>, IValueFactory<PNLSettings>, IObservable<PNLSettings>, ITemplateSelectorKey {
PNLCalculationType _calculation_type;
PNLSettings _last_loaded;
public PNLSettingsVM(PNLSettings settings) {
core_settings_vm = new GridSettingsVM();
columns = core_settings_vm.columns.CastObservable<GridColumnSettingsVM, PNLColumnSettingsVM>();
load(settings);
_dispose = ObservableAutoWrapper.connect_item_hooks(columns, col => col.connect_default_values(core_settings_vm));
}
public PNLAnchorHierarchyVM anchor_hierarcy { get; } = new PNLAnchorHierarchyVM();
public GridSettingsVM core_settings_vm { get; }
public PNLGroupRowCellAppearanceVM group_row_defaults { get; } = new PNLGroupRowCellAppearanceVM();
public PNLHeaderRowCellAppearanceVM header_row_defaults { get; } = new PNLHeaderRowCellAppearanceVM();
public PNLCalculationType calculation_type { get => _calculation_type; set => Set(ref _calculation_type, value); }
public IEnumerable<PNLCalculationType> calculation_types => EnumHelper.get_values_except(PNLCalculationType.None, PNLCalculationType.Same, PNLCalculationType.Opposite);
public IReadOnlyObservableCollection<PNLColumnSettingsVM> columns { get; }
string ITemplateSelectorKey.key => GridSettingsTemplateKeys.PNL;
public PNLSettings get_result() {
var s = _last_loaded == null ? new PNLSettings() : SparkUtils.clone_proto(_last_loaded);
s.calculation_type = _calculation_type;
s.grid = core_settings_vm.get_result();
// move anchor cols to the left, order wrt grouping hierarchy
var new_cols = new Lazy<List<GridColumnSettings>>(() => s.grid.columns.ToList());
for (int i = 0; i < s.grid.columns.Count; i++) {
var key = s.grid.columns[i].strong_id<ColumnKeys>();
if (_anchor_cache.TryGetValue(key, out var a) && a.anchor_index.HasValue) {
// anchor index is 1 based, subtract 1 to get 0 based
int anchor_index = a.anchor_index.Value - 1;
if (anchor_index != i && anchor_index >= 0 && anchor_index < s.grid.columns.Count) {
new_cols.Value.RemoveAt(i);
new_cols.Value.Insert(anchor_index, s.grid.columns[i]);
}
int anchor_index = anchor_hierarcy.value.indexOf(key);
if (anchor_index != i && anchor_index >= 0) {
new_cols.Value.RemoveAt(i);
new_cols.Value.Insert(anchor_index, s.grid.columns[i]);
}
}
// rule-based animations
var trigger_anims = core_settings_vm.columns
.OfType<PNLColumnVM>()
.SelectMany(c => c.get_triggers())
.GroupBy(tup => tup.expression)
.Where(g => CoreExpressionEvaluator<PNLRowData>.validate(g.Key))
.Select(g => (
expression: g.Key,
animations: g.ToArray()))
.ToList();
var normalized_anims = trigger_anims
.Where(tup => CoreExpressionEvaluator<PNLNormalizedData>.validate(tup.expression))
.ToList();
s = s.with(anchors: anchor_items.Select(vm => vm.key),
grid: new_cols.IsValueCreated ? s.grid.with(columns: new_cols.Value) : null,
group_columns: core_settings_vm.columns.OfType<PNLColumnVM>().Select(c => {
var col_settings = c.create();
col_settings = c.group_column_vm.apply_to(col_settings);
return col_settings;
}),
header_columns: core_settings_vm.columns.OfType<PNLColumnVM>().Select(c => {
var col_settings = c.create();
col_settings = c.header_column_vm.apply_to(col_settings);
return col_settings;
}),
animations: trigger_anims.Select(
ta => new PNLAnimationTriggerData {
expression = ta.expression,
animations = Array.ConvertAll(ta.animations, t => t.row)
}),
group_header_animations: normalized_anims
.Select(
ta => new PNLAnimationTriggerData {
expression = ta.expression,
animations = Array.ConvertAll(ta.animations, t => t.group)
}),
header_row_animations: normalized_anims.Select(
ta => new PNLAnimationTriggerData {
expression = ta.expression,
animations = Array.ConvertAll(ta.animations, t => t.header)
}));
s.header_row_display = header_row_display;
s.header_cell_appearance = PNLCellAppearanceProto.from_appearance(header_row_defaults.to_appearance());
// group
var (group_cell_appearance, group_row_background_colors) = group_row_defaults.create();
s.group_cell_appearance = PNLCellAppearanceProto.from_appearance(group_cell_appearance);
s.group_background_colors.SetItems(group_row_background_colors.Select(c => c.rgb_color()));
//
return s;
}
public void load(PNLSettings value) {
_last_loaded = value;
header_row_defaults.load(value.header_cell_appearance.to_appearance());
group_row_defaults.load_from((value.group_cell_appearance.to_appearance(), value.group_background_colors.WrapSelectorRO(c => c.argb_color())));
var column_map = _map_column_data(value.grid.columns, value.animations);
var group_column_map = _map_column_data(value.group_columns, value.group_header_animations);
var header_column_map = _map_column_data(value.header_columns, value.header_row_animations);
core_settings_vm.load_from(value.grid);
for (int i = 0; i < core_settings_vm.columns.Count; i++) {
var col_vm = core_settings_vm.columns[i] as PNLColumnVM ?? throw new InvalidOperationException($"In {nameof(PNLSettingsVM)}, all columns should be of type {typeof(PNLColumnVM)}.");
var key = EnumHelper.parse<ColumnKeys>(col_vm.id);
var (col_settings, trigger_animations) = column_map[key];
var group_data = group_column_map.GetNullable(key);
var header_data = header_column_map.GetNullable(key);
col_vm.load_from(col_settings, trigger_animations, group_data?.settings, group_data?.triggers, header_data?.triggers, header_data?.settings);
}
using (_sync_anchors.defer_execution())
anchor_items.WrapSelector(a => a.key, k => _anchor_cache[k]).sync_with(value.anchors.Intersect(PNLSettings.anchorable_columns));
header_row_display = value.header_row_display;
calculation_type = value.calculation_type;
}
PNLSettings IValueFactory<PNLSettings>.create() => get_result();
void IValueFactory<PNLSettings>.load_from(PNLSettings value) => load(value);
IDisposable IObservable<PNLSettings>.Subscribe(IObserver<PNLSettings> observer) => throw new NotImplementedException();
static Dictionary<ColumnKeys, (GridColumnSettings settings, Dictionary<string, PNLAnimation> triggers)> _map_column_data(IEnumerable<GridColumnSettings> columns, IEnumerable<PNLAnimationTriggerData> triggers) {
var map = columns.ToDictionary(
c => c.strong_id<ColumnKeys>(),
c => (settings: c, triggers: new Dictionary<string, PNLAnimation>()));
foreach (var trig in triggers) {
foreach (var anim in trig.animations) {
if (map.TryGetValue(anim.column, out var col_data))
col_data.triggers[trig.expression] = anim;
}
}
return map;
}
void _append_or_remove_anchor(ColumnKeys key, bool include) {
if (!_anchor_cache.TryGetValue(key, out var vm))
throw new KeyNotFoundException($"Column key '{key}' is not anchorable (expected one of: {string.Join(", ", PNLSettings.anchorable_columns)}).");
if (vm.include == include)
return;
if (include) {
if (anchor_items.Any(a => a.key == key))
throw new InvalidOperationException($"{nameof(anchor_items)} already contains an element with key '{key}.'");
anchor_items.Add(vm);
vm.anchor_index = anchor_items.Count;
} else {
if (!anchor_items.Remove(vm))
throw new InvalidOperationException($"{nameof(anchor_items)} does not contain an element with key '{key}.'");
vm.anchor_index = null;
}
_sync_anchors.execute();
}
public class AnchorItemVM : ViewModelBase, IEquatable<AnchorItemVM> {
readonly PNLSettingsVM _owner;
int? _anchor_index;
public AnchorItemVM(PNLSettingsVM owner, ColumnKeys key) {
_owner = owner;
this.key = key;
}
public ColumnKeys key { get; }
public bool include { get => anchor_index.HasValue; set => _owner._append_or_remove_anchor(key, value); }
public int? anchor_index { get => _anchor_index; set => Set(ref _anchor_index, value); }
public void refresh() =>
RaisePropertyChanged(nameof(include), nameof(anchor_index));
public bool Equals(AnchorItemVM other) =>
other != null && key == other.key;
public override bool Equals(object obj) =>
Equals(obj as AnchorItemVM);
public override int GetHashCode() =>
key.GetHashCode();
public override string ToString() =>
EnumHelper.get_display_name(key);
}
}
}
// rule-based animations
var trigger_anims = columns
.SelectMany(c => c.get_triggers())
.GroupBy(tup => tup.expression)
.Where(g => CoreExpressionEvaluator<PNLRowData>.validate(g.Key))
.Select(g => (
expression: g.Key,
animations: g.ToArray()))
.ToList();
var normalized_anims = trigger_anims
.Where(tup => CoreExpressionEvaluator<PNLNormalizedData>.validate(tup.expression))
.ToList();
s = s.with(anchors: anchor_hierarcy.value,
grid: new_cols.IsValueCreated ? s.grid.with(columns: new_cols.Value) : null,
group_columns: columns.Select(c => {
var col_settings = c.create();
col_settings = c.default_cell_appearance.group_vm.apply_to(col_settings);
return col_settings;
}),
header_columns: columns.Select(c => {
var col_settings = c.create();
col_settings = c.default_cell_appearance.header_vm.apply_to(col_settings);
return col_settings;
}),
animations: trigger_anims.Select(
ta => new PNLAnimationTriggerData {
expression = ta.expression,
animations = Array.ConvertAll(ta.animations, t => t.row)
}),
group_header_animations: normalized_anims
.Select(
ta => new PNLAnimationTriggerData {
expression = ta.expression,
animations = Array.ConvertAll(ta.animations, t => t.group)
}),
header_row_animations: normalized_anims.Select(
ta => new PNLAnimationTriggerData {
expression = ta.expression,
animations = Array.ConvertAll(ta.animations, t => t.header)
}));
s.header_row_display = header_row_defaults.display;
s.header_cell_appearance = PNLCellAppearanceProto.from_appearance(header_row_defaults.to_appearance());
// group
var (group_cell_appearance, group_row_background_colors) = group_row_defaults.create();
s.group_cell_appearance = PNLCellAppearanceProto.from_appearance(group_cell_appearance);
s.group_background_colors.SetItems(group_row_background_colors.Select(c => c.rgb_color()));
//
return s;
}
public void load(PNLSettings value) {
_last_loaded = value;
header_row_defaults.load(value.header_cell_appearance.to_appearance());
group_row_defaults.load((value.group_cell_appearance.to_appearance(), value.group_background_colors.WrapSelectorRO(c => c.argb_color())));
var column_map = _map_column_data(value.grid.columns, value.animations);
var group_column_map = _map_column_data(value.group_columns, value.group_header_animations);
var header_column_map = _map_column_data(value.header_columns, value.header_row_animations);
core_settings_vm.load(value.grid, false);
using (core_settings_vm.begin_change_columns()) {
core_settings_vm.columns.SetItems(value.grid.columns.Select(c => {
var key = EnumHelper.parse<ColumnKeys>(c.id);
var vm = new PNLColumnSettingsVM(key);
var (col_settings, trigger_animations) = column_map[key];
var group_data = group_column_map.GetNullable(key);
var header_data = header_column_map.GetNullable(key);
vm.load_from(col_settings, trigger_animations, group_data?.settings, group_data?.triggers, header_data?.triggers, header_data?.settings);
return vm;
}));
}
anchor_hierarcy.value = value.anchors.Intersect(PNLSettings.anchorable_columns);
header_row_defaults.display = value.header_row_display;
calculation_type = value.calculation_type;
}
PNLSettings IValueFactory<PNLSettings>.create() => get_result();
void IValueFactory<PNLSettings>.load_from(PNLSettings value) => load(value);
IDisposable IObservable<PNLSettings>.Subscribe(IObserver<PNLSettings> observer) => throw new NotImplementedException();
static Dictionary<ColumnKeys, (GridColumnSettings settings, Dictionary<string, PNLAnimation> triggers)> _map_column_data(IEnumerable<GridColumnSettings> columns, IEnumerable<PNLAnimationTriggerData> triggers) {
var map = columns.ToDictionary(
c => c.strong_id<ColumnKeys>(),
c => (settings: c, triggers: new Dictionary<string, PNLAnimation>()));
foreach (var trig in triggers) {
foreach (var anim in trig.animations) {
if (map.TryGetValue(anim.column, out var col_data))
col_data.triggers[trig.expression] = anim;
}
}
return map;
}
}
}
@@ -291,7 +291,7 @@ namespace bts.shell.pnl {
};
public void load_from(PNLSettings settings) {
selection_highlight = settings.grid.selection_highlight.to_brush();
selection_highlight = settings.grid.selection_highlight.to_argb_brush();
grid_background_color = settings.grid.background;
calculation_type = settings.calculation_type;
if (settings.filters != null) {
@@ -383,7 +383,7 @@ namespace bts.shell.pnl {
// hide implicit cols
state.grid = _hide_cols(state.grid, anchors);
return new FillsVM(
_bus, _orders_model, Observable.Return(state), new WindowSettingsManager<GridSettings>.DisconnectedImpl(state.grid));
_bus, _orders_model, state, new WindowSettingsManager<GridSettings>.DisconnectedImpl(state.grid));
}
public FillsVM mk_fills_vm(PNLRowCollectionViewGroup group, ColumnKeys key) {
@@ -393,7 +393,7 @@ namespace bts.shell.pnl {
// hide implicit cols
state.grid = _hide_cols(state.grid, anchors.WrapSubRange(0, group.level));
return new FillsVM(
_bus, _orders_model, Observable.Return(state), _visible_fills_source(), new WindowSettingsManager<GridSettings>.DisconnectedImpl(state.grid));
_bus, _orders_model, state, _visible_fills_source(), new WindowSettingsManager<GridSettings>.DisconnectedImpl(state.grid));
}
public FillsVM mk_fills_vm(PNLHeaderRowDisplay header, ColumnKeys key) {
@@ -419,7 +419,7 @@ namespace bts.shell.pnl {
break;
}
return new FillsVM(
_bus, _orders_model, Observable.Return(state), src, new WindowSettingsManager<GridSettings>.DisconnectedImpl(state.grid));
_bus, _orders_model, state, src, new WindowSettingsManager<GridSettings>.DisconnectedImpl(state.grid));
}
public OrderBookVM mk_orderbook_vm(PNLHeaderRowDisplay header, ColumnKeys key) {
@@ -0,0 +1,174 @@
<UserControl
x:Class="mbts.ui.spark.PNLSettingsView"
x:ClassModifier="internal"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:pnl="clr-namespace:bts.shell.pnl"
xmlns:u="clr-namespace:mbts.ui"
xmlns:c="clr-namespace:mbts.ui.controls"
xmlns:cv="clr-namespace:mbts.ui.converters"
xmlns:usp="clr-namespace:mbts.ui.spark"
xmlns:vm="clr-namespace:mbts.ui.viewmodels"
Content="{Binding}"
>
<UserControl.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<usp:GridSettingsResources/>
</ResourceDictionary.MergedDictionaries>
<DataTemplate DataType="{x:Type pnl:PNLColumnSettingsVM}">
<ItemsControl ItemsSource="{Binding parameters}"/>
</DataTemplate>
<DataTemplate DataType="{x:Type vm:OrderedItemParameterVM}">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<CheckBox x:Name="IncludeToggle" VerticalAlignment="Top" Margin="0,4,0,0" Width="22" IsChecked="{Binding value}">
<CheckBox.Height>
<Binding Path="ActualHeight" ElementName="AnchorBorder">
<Binding.Converter>
<!--Offset Margin.Top-->
<cv:DoubleSubtractionConverter Value="4"/>
</Binding.Converter>
</Binding>
</CheckBox.Height>
</CheckBox>
<Border x:Name="AnchorBorder" Grid.Column="1" BorderBrush="Transparent" BorderThickness="0,4,4,4" MinWidth="120" Padding="6,8" Cursor="{StaticResource {x:Static u:CursorsEx.GrabKey}}">
<u:StateManager.Groups>
<u:StateGroupsManager AttachToMembers="True">
<u:StateDefinitionCollection>
<u:MouseStateDefinition MouseSource="{Binding Group.Target, RelativeSource={RelativeSource Self}}"/>
<u:CheckedStateDefinition Source="{u:DirectReference IncludeToggle}"/>
</u:StateDefinitionCollection>
</u:StateGroupsManager>
</u:StateManager.Groups>
<VisualStateManager.VisualStateGroups>
<VisualStateGroup Name="MouseStates">
<VisualState Name="Normal"/>
<VisualState Name="MouseOver"/>
<VisualState Name="Pressed">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="Cursor">
<DiscreteObjectKeyFrame KeyTime="0" Value="{StaticResource {x:Static u:CursorsEx.GrabbingKey}}"/>
</ObjectAnimationUsingKeyFrames>
<ColorAnimation Storyboard.TargetProperty="(Panel.Background).(SolidColorBrush.Color)" Duration="0" To="{x:Static u:BTSColors.header_alternate_color}"/>
</Storyboard>
</VisualState>
</VisualStateGroup>
<VisualStateGroup Name="CheckedStates">
<VisualState Name="Unchecked"/>
<VisualState Name="Checked">
<Storyboard>
<DoubleAnimation Storyboard.TargetName="KeyIcon" Storyboard.TargetProperty="Opacity" BeginTime="0" Duration="0" To="1"/>
<DoubleAnimation Storyboard.TargetName="AnchorIndex" Storyboard.TargetProperty="Opacity" BeginTime="0" Duration="0" To="1"/>
<ColorAnimation Storyboard.TargetName="ColumnNameText" Storyboard.TargetProperty="Foreground.Color" BeginTime="0" Duration="0" To="Gold"/>
</Storyboard>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
<Border.Resources>
<u:CursorsEx/>
</Border.Resources>
<Border.Background>
<SolidColorBrush Color="{x:Static u:BTSColors.header_color}"/>
</Border.Background>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Image x:Name="KeyIcon" Height="11" Opacity="0">
<Image.Source>
<DrawingImage>
<DrawingImage.Drawing>
<GeometryDrawing Geometry="{Binding Source={StaticResource {x:Static u:Icons.KeyGeometryKey}}}" Brush="Gold"/>
</DrawingImage.Drawing>
</DrawingImage>
</Image.Source>
</Image>
<TextBlock x:Name="AnchorIndex" HorizontalAlignment="Left" VerticalAlignment="Bottom" Margin="16,0,0,-2" FontSize="8.5" Foreground="Gold" Opacity="0">
<TextBlock.Text>
<Binding Path="checked_index">
<Binding.Converter>
<cv:IntAdditionConverter Value="1"/>
</Binding.Converter>
</Binding>
</TextBlock.Text>
</TextBlock>
<TextBlock x:Name="ColumnNameText" Grid.Column="1" Margin="6,0,0,0" HorizontalAlignment="Left" VerticalAlignment="Center" FontWeight="Bold" Text="{Binding}">
<TextBlock.Foreground>
<SolidColorBrush Color="{x:Static u:BTSColors.header_text_color}"/>
</TextBlock.Foreground>
</TextBlock>
</Grid>
</Border>
</Grid>
</DataTemplate>
<DataTemplate DataType="{x:Type pnl:PNLAnchorHierarchyVM}">
<Grid Margin="12,8">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<TextBlock FontStyle="Italic">
<Run>
Check the groups you would like to use.
</Run>
<LineBreak/>
<Run>
Drag and drop them to change the grouping order.
</Run>
</TextBlock>
<ItemsControl
Grid.Row="1"
AllowDrop="True"
HorizontalAlignment="Left"
ItemsSource="{Binding items}"
Margin="0,8,0,0"
Loaded="_anchor_items_loaded"
>
</ItemsControl>
</Grid>
</DataTemplate>
</ResourceDictionary>
</UserControl.Resources>
<UserControl.ContentTemplate>
<DataTemplate DataType="{x:Type pnl:PNLSettingsVM}">
<Grid>
<u:BindingProxy x:Name="ActionMenuItemsViewProxy" Value="{DynamicResource ActionMenuItemsView}"/>
<c:ActionMenu ItemsSource="{Binding Path='(u:BindingProxy.Value).(CollectionViewSource.View)', ElementName=ActionMenuItemsViewProxy}">
<ItemsControl.GroupStyle>
<GroupStyle ContainerStyle="{StaticResource ComboBox.DelineatedGroupContainerStyle.DynamicFill}"/>
</ItemsControl.GroupStyle>
</c:ActionMenu>
<TabControl MinWidth="600" Margin="0,2,0,0" Style="{StaticResource FlatTabControl}">
<TabItem Header="Grid">
<ItemsControl
Grid.IsSharedSizeScope="True"
ItemsSource="{Binding core_settings_vm.parameters}"
/>
</TabItem>
<TabItem Header="Grouping" Content="{Binding anchor_hierarcy}"/>
<TabItem Header="Group Row Display">
<ItemsControl
Grid.IsSharedSizeScope="True"
ItemsSource="{Binding group_row_defaults.parameters}"
/>
</TabItem>
<TabItem Header="Header Row Display">
<ItemsControl
Grid.IsSharedSizeScope="True"
ItemsSource="{Binding header_row_defaults.parameters}"
/>
</TabItem>
<TabItem Header="Columns" Content="{Binding core_settings_vm}" ContentTemplate="{StaticResource ColumnsTabContent}"/>
</TabControl>
</Grid>
</DataTemplate>
</UserControl.ContentTemplate>
</UserControl>
@@ -0,0 +1,129 @@
using bts;
using mbts.ui.controls.adorners;
using mbts.ui.extensions;
using mbts.utils;
using System;
using System.Collections;
using System.Reactive.Disposables;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media;
namespace mbts.ui.spark {
/// <summary>
/// Interaction logic for PNLSettingsView.xaml
/// </summary>
partial class PNLSettingsView : UserControl {
public PNLSettingsView() =>
InitializeComponent();
IDisposable _anchor_items_drag_drop_behavior;
void _anchor_items_loaded(object sender, RoutedEventArgs e) {
// connecting this drag/drop behavior is idempotent, just in case Loaded happens twice
Disposable.dispose(ref _anchor_items_drag_drop_behavior);
if (!(sender is ItemsControl ic))
return;
var root_grid = Window.GetWindow(ic)?.Content as Grid;
bool is_dragging = false;
IDisposable drag_op = null;
var cursor_resources = new Lazy<CursorsEx>();
_anchor_items_drag_drop_behavior = Disposable.Create(() => Disposable.dispose(ref drag_op));
DragDropHelper.subscribe_drag_start(ic).Subscribe(pt => {
if (!is_dragging &&
ic.InputHitTest(pt) is DependencyObject d &&
ic.ContainerFromElement(d) is FrameworkElement item_view &&
ic.ItemContainerGenerator.ItemFromContainer(item_view) is object item &&
item != DependencyProperty.UnsetValue) {
//
item_view.SetCurrentValue(OpacityProperty, 0.5);
is_dragging = true;
var sz = item_view.RenderSize;
var dv = new DrawingVisual();
using (var dc = dv.RenderOpen())
dc.DrawRoundedRectangle(new VisualBrush(item_view) { Stretch = Stretch.Uniform }, null, new Rect(sz), 0, 0);
SetCurrentValue(CursorProperty, Cursors.ScrollWE);
SetCurrentValue(AllowDropProperty, true);
is_dragging = true;
Disposable.replace(ref drag_op, new CompositeDisposable {
MouseTraceAdorner.trace_drag(root_grid, dv, sz),
DragDropHelper.connect_vertical_insertion_adorner(ic, root_grid),
this.SubscribePreviewDrop(on_drop),
this.SubscribePreviewMouseUp(on_preview_mouse_up),
this.SubscribePreviewDragOver(on_drag),
this.SubscribePreviewDragLeave(on_drag),
this.SubscribePreviewDragEnter(on_drag),
() => {
ClearValue(CursorProperty);
ClearValue(AllowDropProperty);
item_view.ClearValue(OpacityProperty);
is_dragging = false;
}});
DragDrop.DoDragDrop(this, new DataObject(DataFormats.StringFormat, item), DragDropEffects.Move);
}
});
void on_drag(object s, DragEventArgs de) =>
de.Effects = DragDropEffects.Move;
void on_drop(object _, DragEventArgs drop_e) {
drop_e.Handled = true;
if (!is_dragging)
return;
Disposable.dispose(ref drag_op);
if (drop_e.Data.GetDataPresent(DataFormats.StringFormat) &&
drop_e.Data.GetData(DataFormats.StringFormat) is object from &&
ic.ItemsSource is IList anchor_items &&
anchor_items.Count > 1) {
//
var pt = drop_e.GetPosition(this);
if (!(InputHitTest(pt) is DependencyObject hit))
return;
try {
int from_i = anchor_items.IndexOf(from);
// Set the container to the first item who's right edge is left of pt.X (or the last item if none satisfy that condition).
// The logic presumes that the items are laid out in a single, horizontal line, from left to right
// (not the most elegant way to get the drop target, but it's effective).
int to_i = -1;
if (!(ic.ContainerFromElement(hit) is FrameworkElement drop_target)) {
for (to_i = 0; to_i < ic.Items.Count; to_i++) {
if (ic.ItemContainerGenerator.ContainerFromIndex(to_i) is FrameworkElement c) {
drop_target = c;
var tl = c.TranslatePoint(new Point(0, 0), this);
var bottom_edge = tl.Y + c.ActualHeight;
if (bottom_edge >= pt.Y)
break;
}
}
} else if (drop_target.DataContext is object to) {
to_i = anchor_items.IndexOf(to);
var drop_target_pt = TranslatePoint(pt, drop_target);
if (drop_target_pt.Y > drop_target.ActualHeight / 2) {
// target is "Between drop_target and the item below it"
if (from_i > to_i)
to_i++;
} else {
// target is "Between drop_target and the item above it"
if (from_i < to_i) {
to_i--;
}
}
}
if (to_i < 0)
return;
if (to_i >= anchor_items.Count)
to_i = anchor_items.Count - 1;
if (from_i >= 0)
anchor_items.Move(from_i, to_i);
} catch (Exception ex) {
Log.Ui.warn($"Error dropping anchor hiearchy item in pnl settings dialog: {ex}.");
}
}
}
void on_preview_mouse_up(object _, MouseButtonEventArgs __) =>
Disposable.dispose(ref drag_op);
}
}
}
@@ -1,65 +1,15 @@
using bts.api;
using bts.api.orderman;
using bts.api.orderman;
using bts.shell;
using bts.utils;
using mbts.spark;
using mbts.ui;
using mbts.ui.settings;
using mbts.ui.utility;
using mbts.utils;
using ProtoBuf;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reactive.Linq;
using OrderType = bts.api.orderman.CustomOrderDefinition.CustomOneofCase;
using System.ComponentModel;
using PTOC = mbts.OrderRecordTuplePredicate.PredicateTypeOneofCase;
namespace mbts {
static class OrderRecordTuplePredicateVMFactory {
// TODO: uncomment the remaining cases when ready
public static Dictionary<PTOC, ITemplatedPropertyWrapperVM<OrderRecordTuplePredicate>> get_oneofs(IBus bus, OrdersModel orders_model) {
return new Dictionary<PTOC, ITemplatedPropertyWrapperVM<OrderRecordTuplePredicate>> {
{ PTOC.FilledEvent, wrap(rp => rp.filled_event, (rp, p) => rp.filled_event = p, p => p.predicate_type_case, OrderFilledEventPredicateVMFactory.get_oneofs(bus)) },
{ PTOC.CompletedEvent, wrap(rp => rp.completed_event, (rp, p) => rp.completed_event = p, p => p.predicate_type_case, OrderCompletedEventPredicateVMFactory.get_oneofs(bus)) },
{ PTOC.CanceledEvent, wrap(rp => rp.canceled_event, (rp, p) => rp.canceled_event = p, p => p.predicate_type_case, OrderCanceledEventPredicateVMFactory.get_oneofs(bus)) },
//{ PTOC.Security, wrap(rp => rp.security, (rp, p) => rp.security = p, p => p.predicate_type_case, SecurityCriteriaVMFactory.get_oneofs(bus)) },
//{ PTOC.Time, wrap(rp => rp.time_created, (rp, p) => rp.time_created = p, t => t.predicate_type_case, TimePredicateVMFactory.get_oneofs()) },
//{ PTOC.LastChange, wrap(rp => rp.last_change, (rp, p) => rp.last_change = p, t => t.predicate_type_case, TimePredicateVMFactory.get_oneofs()) },
//{ PTOC.User, wrap(rp => rp.user, (rp, p) => rp.user = p, p => p.predicate_type_case, DiscreteValuesPredicateVMFactory.get_oneofs<uint, uint, uint?, uint[], DiscreteUIntPredicate>(
// RXCollection.Create(orders_model.factory.subscribe_user_ids().Select(tups => tups.Select(convert_name_id))), id => id, id => id.Value, id => id, ids => ids, ids => ids.ToArray())) },
//{ PTOC.Account, wrap(rp => rp.account, (rp, p) => rp.account = p, p => p.predicate_type_case, DiscreteValuesPredicateVMFactory.get_oneofs<uint, uint, uint?, uint[], DiscreteUIntPredicate>(
// RXCollection.Create(orders_model.factory.subscribe_account_ids().Select(tups => tups.Select(convert_name_id))), id => id, id => id.Value, id => id, ids => ids, ids => ids.ToArray())) },
//{ PTOC.Exchange, wrap(rp => rp.exchange, (rp, p) => rp.exchange = p, p => p.predicate_type_case, DiscreteValuesPredicateVMFactory.get_oneofs_value_type<Venue, ExchangePredicate>(EnumHelper.get_values_except(Venue.Unset))) },
//{ PTOC.TimeInForce, wrap(rp => rp.time_in_force, (rp, p) => rp.time_in_force = p, p => p.predicate_type_case, DiscreteValuesPredicateVMFactory.get_oneofs_value_type<TimeInForce, TimeInForcePredicate>(EnumHelper.get_values_except(TimeInForce.Undefined))) },
//{ PTOC.Side, wrap(rp => rp.side, (rp, p) => rp.side = p, p => p.predicate_type_case, SidePredicateVMFactory.get_oneofs()) },
//{ PTOC.Price, wrap(rp => rp.price, (rp, p) => rp.price = p, p => p.predicate_type_case, DoublePredicateVMFactory.get_oneofs()) },
//{ PTOC.Size, wrap(rp => rp.quantity, (rp, p) => rp.quantity = p, p => p.predicate_type_case, UIntPredicateVMFactory.get_oneofs()) },
//{ PTOC.Filled, wrap(rp => rp.filled_quantity, (rp, p) => rp.filled_quantity = p, p => p.predicate_type_case, UIntPredicateVMFactory.get_oneofs()) },
//{ PTOC.AvgFilled, wrap(rp => rp.avg_price, (rp, p) => rp.avg_price = p, p => p.predicate_type_case, DoublePredicateVMFactory.get_oneofs()) },
//{ PTOC.LastPrice, wrap(rp => rp.last_price, (rp, p) => rp.last_price = p, p => p.predicate_type_case, DoublePredicateVMFactory.get_oneofs()) },
//{ PTOC.OrderType, wrap(rp => rp.order_type, (rp, p) => rp.order_type = p, p => p.predicate_type_case, DiscreteValuesPredicateVMFactory.get_oneofs_value_type<OrderType, OrderTypePredicate>(EnumHelper.get_values_except(OrderType.None))) },
//{ PTOC.SubType, wrap(rp => rp.order_sub_type, (rp, p) => rp.order_sub_type = p, p => p.predicate_type_case, DiscreteValuesPredicateVMFactory.get_oneofs_value_type<OrderSubType, OrderSubTypePredicate>(EnumHelper.get_values<OrderSubType>())) },
//{ PTOC.Status, wrap(rp => rp.order_status, (rp, p) => rp.order_status = p, p => p.predicate_type_case, DiscreteValuesPredicateVMFactory.get_oneofs_value_type<OrderStatus, OrderStatusPredicate>(EnumHelper.get_values<OrderStatus>())) },
//{ PTOC.LastMessage, wrap(rp => rp.last_message, (rp, p) => rp.last_message = p, p => p.predicate_type_case, StringPredicateVMFactory.get_oneofs()) },
//{ PTOC.SecurityKey, wrap(rp => rp.security_key, (rp, p) => rp.security_key = p, p => p.predicate_type_case, UIntPredicateVMFactory.get_oneofs()) },
//{ PTOC.OrderKey, wrap(rp => rp.order_key, (rp, p) => rp.order_key = p, p => p.predicate_type_case, UIntPredicateVMFactory.get_oneofs()) },
//{ PTOC.ParentKey, wrap(rp => rp.parent_key, (rp, p) => rp.parent_key = p, p => p.predicate_type_case, UIntPredicateVMFactory.get_oneofs()) },
};
//IViewValue<uint> convert_name_id((string name, uint id) tup) =>
// new ViewValueTuple<uint>(tup.id, tup.name);
ITemplatedPropertyWrapperVM<OrderRecordTuplePredicate> wrap<TChildPredicate, TChildPredicateOneof>(
Func<OrderRecordTuplePredicate, TChildPredicate> get,
Action<OrderRecordTuplePredicate, TChildPredicate> set,
Func<TChildPredicate, TChildPredicateOneof> key,
Dictionary<TChildPredicateOneof, ITemplatedPropertyWrapperVM<TChildPredicate>> oneofs) where TChildPredicateOneof : struct where TChildPredicate : new() =>
//
PredicateVMWrapper.wrap_child_oneof<OrderRecordTuplePredicate, TChildPredicate, TChildPredicateOneof>(get, set, key, oneofs);
}
}
[ProtoContract]
class OrderRecordTuplePredicate : IEquatable<OrderRecordTuplePredicate> {
SecurityPredicate _security;
@@ -120,11 +70,11 @@ namespace mbts {
OrderKey,
[PredicateCase(is_toastable:false)]
ParentKey,
[PredicateCase("When order gets a fill", PredicateCategory.Event)]
[PredicateCase("Order Filled", PredicateCategory.Event)]
FilledEvent,
[PredicateCase("When order is completed", PredicateCategory.Event)]
[PredicateCase("Order Completed", PredicateCategory.Event)]
CompletedEvent,
[PredicateCase("When order is canceled", PredicateCategory.Event)]
[PredicateCase("Order Canceled", PredicateCategory.Event)]
CanceledEvent,
[PredicateCase(is_toastable:false)]
AttachedTargetOrderKey
@@ -363,6 +313,7 @@ namespace mbts {
[ProtoContract]
class OrderRecordTuplePredicateAnimations : IPredicateAnimationsData<OrderSecurity, OrderRecordTuplePredicate> {
const bool _default_is_enabled = true;
[ProtoMember(1)]
OrderRecordTuplePredicate _predicate;
[ProtoMember(2)]
@@ -371,19 +322,31 @@ namespace mbts {
List<RowAnimation> _row_animations;
[ProtoMember(4)]
List<SoundNotification> _sound_animations;
// is_enabled
[ProtoMember(5)]
[DefaultValue(_default_is_enabled)]
bool _is_enabled;
public OrderRecordTuplePredicateAnimations() { }
public OrderRecordTuplePredicateAnimations() =>
_is_enabled = _default_is_enabled;
public OrderRecordTuplePredicateAnimations(OrderRecordTuplePredicate predicate, IEnumerable<CellAnimation> cell_animations, IEnumerable<RowAnimation> row_animations, IEnumerable<SoundNotification> sound_animations) {
public OrderRecordTuplePredicateAnimations(
OrderRecordTuplePredicate predicate,
IEnumerable<CellAnimation> cell_animations,
IEnumerable<RowAnimation> row_animations,
IEnumerable<SoundNotification> sound_animations,
bool is_enabled = true) {
_predicate = predicate;
_cell_animations = new List<CellAnimation>(cell_animations);
_row_animations = new List<RowAnimation> ( row_animations );
_sound_animations = new List<SoundNotification> ( sound_animations );
_cell_animations = new List<CellAnimation>(cell_animations.empty_if_null());
_row_animations = new List<RowAnimation>(row_animations.empty_if_null());
_sound_animations = new List<SoundNotification>(sound_animations.empty_if_null());
_is_enabled = is_enabled;
}
public OrderRecordTuplePredicate predicate_data => _predicate;
public IReadOnlyList<CellAnimation> cell_animations => _cell_animations ?? (_cell_animations = new List<CellAnimation>());
public IReadOnlyList<RowAnimation> row_animations => _row_animations ?? (_row_animations = new List<RowAnimation>());
public IReadOnlyList<SoundNotification> sound_notifications => _sound_animations ?? (_sound_animations = new List<SoundNotification>());
public bool is_enabled => _is_enabled;
}
}
+4 -1
View File
@@ -41,9 +41,12 @@
<ResourceDictionary Source="/view/Controls/DataGridEx/Filters/Views/DoubleMinMaxFilterControl.xaml"/>
<ResourceDictionary Source="/view/Controls/DataGridEx/Filters/Views/DiscreteValuesFilterControl.xaml"/>
<ResourceDictionary Source="/view/Controls/DataGridEx/Filters/Views/TimeFilterControl.xaml"/>
<ResourceDictionary Source="/view/Controls/ActionMenu/ActionMenu.xaml"/>
<ResourceDictionary Source="/Modules/Spreader/ExecutionStrategyEditor.xaml"/>
<ResourceDictionary Source="/view/Controls/Order/AlgoContentTemplateSelector.xaml"/>
<ResourceDictionary Source="/view/Controls/Order/AlgoPickerNodeView.xaml"/>
<ResourceDictionary Source="/view/Controls/Popup/PopupControl.xaml"/>
<ResourceDictionary Source="/view/Controls/ColorPicker/TabularColorPicker.xaml"/>
</ResourceDictionary.MergedDictionaries>
<Style TargetType="ScrollViewer">
@@ -559,4 +562,4 @@
<Setter Property="Command" Value="{Binding command}"/>
<Setter Property="Icon" Value="{Binding icon, Converter={StaticResource IconConverter}}"/>
</Style>
</ResourceDictionary>
</ResourceDictionary>
@@ -115,20 +115,29 @@ namespace mbts.ui.controls {
}
}
internal class Aggregator<T> : IDisposable, IList<(IReadOnlyCollection<T> items, INotifyCollectionChanged notifier)>, IReadOnlyList<(IReadOnlyCollection<T> items, INotifyCollectionChanged notifier)> {
// TODO: simplifiy this class to implement IList<IObservableCollection<T>>, or something other than the tuple
// it is proving to be more trouble than its worth; the onus should be on callers to wrap/convert their collections if they do not implement IObservableCollection<T>.
//internal class Aggregator<T> : IDisposable, IList<(IReadOnlyCollection<T> items, INotifyCollectionChanged notifier)>, IReadOnlyList<(IReadOnlyCollection<T> items, INotifyCollectionChanged notifier)> {
internal class Aggregator<T> : IDisposable, IReadOnlyList<(IReadOnlyCollection<T> items, INotifyCollectionChanged notifier)> {
readonly ObservableCollection<T> _target;
readonly List<(IReadOnlyCollection<T> items, INotifyCollectionChanged notifier)> _collections = new List<(IReadOnlyCollection<T> items, INotifyCollectionChanged notifier)>();
readonly List<NotifierCollectionWrapper> _collections = new List<NotifierCollectionWrapper>();
readonly IList<(IReadOnlyCollection<T> items, INotifyCollectionChanged notifier)> _collection_tuples;
bool _is_disposed;
public Aggregator(ObservableCollection<T> target, params (IReadOnlyCollection<T> items, INotifyCollectionChanged notifier)[] collections) {
_collection_tuples = _collections.WrapSelector(nc => nc.tuple, null);
_target = target;
foreach (var c in collections)
Add(c);
}
public (IReadOnlyCollection<T> items, INotifyCollectionChanged notifier) this[int index] {
get => _collections[index];
set => Insert(index, value);
get => _collections[index].tuple;
set {
// TODO: optimize with 'Replace' ops instead of Remove/Add
RemoveAt(index);
Insert(index, value);
}
}
public int Count => _collections.Count;
@@ -138,16 +147,24 @@ namespace mbts.ui.controls {
public void Add((IReadOnlyCollection<T> items, INotifyCollectionChanged notifier) collection) {
_check_disposed();
_target.AddRange(collection.items);
_collections.Add(collection);
collection.notifier.CollectionChanged += _on_collection_changed;
var wrapper = new NotifierCollectionWrapper(collection);
_collections.Add(wrapper);
wrapper.CollectionChanged += _on_collection_changed;
}
public bool Remove((IReadOnlyCollection<T> items, INotifyCollectionChanged notifier) collection) {
int num_before = _collections.Count;
_check_disposed();
_remove_collection_items(collection, out int _, out int collection_index);
_remove_collection_items(collection, out int _, out int collection_index, true);
_collections.RemoveAt(collection_index);
return _collections.Count < num_before;
}
bool _remove(NotifierCollectionWrapper wrapper) {
int num_before = _collections.Count;
_check_disposed();
_remove_collection_items(wrapper, out int _, out int collection_index, true);
_collections.RemoveAt(collection_index);
collection.notifier.CollectionChanged -= _on_collection_changed;
return _collections.Count < num_before;
}
@@ -157,43 +174,47 @@ namespace mbts.ui.controls {
if (index == _collections.Count)
Add(collection);
else {
_collections.Insert(index, collection);
_on_collection_changed(collection, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, collection, 0));
collection.notifier.CollectionChanged += _on_collection_changed;
var wrapper = new NotifierCollectionWrapper(collection);
_collections.Insert(index, wrapper);
_on_collection_changed(wrapper, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, wrapper, 0));
wrapper.CollectionChanged += _on_collection_changed;
}
}
public void RemoveAt(int index) {
_check_disposed();
Remove(_collections[index]);
_remove(_collections[index]);
}
public void Clear() {
_check_disposed();
foreach (var c in _collections)
c.notifier.CollectionChanged -= _on_collection_changed;
c.CollectionChanged -= _on_collection_changed;
_collections.Clear();
_target.Clear();
}
public bool Contains((IReadOnlyCollection<T> items, INotifyCollectionChanged notifier) item) =>
_collections.Contains(item);
_collection_tuples.Contains(item);
public void CopyTo((IReadOnlyCollection<T> items, INotifyCollectionChanged notifier)[] array, int arrayIndex) =>
_collections.CopyTo(array, arrayIndex);
_collection_tuples.CopyTo(array, arrayIndex);
public int IndexOf((IReadOnlyCollection<T> items, INotifyCollectionChanged notifier) item) =>
_collections.IndexOf(item);
_collection_tuples.IndexOf(item);
public void Dispose() {
if (!_is_disposed) {
foreach (var c in _collections)
c.notifier.CollectionChanged -= _on_collection_changed;
c.CollectionChanged -= _on_collection_changed;
_is_disposed = true;
}
}
public IEnumerator<(IReadOnlyCollection<T> items, INotifyCollectionChanged notifier)> GetEnumerator() => _collections.GetEnumerator();
public IEnumerator<(IReadOnlyCollection<T> items, INotifyCollectionChanged notifier)> GetEnumerator() {
for (int i = 0; i < _collections.Count; i++)
yield return _collections[i].tuple;
}
IEnumerator IEnumerable.GetEnumerator() => _collections.GetEnumerator();
void _on_collection_changed(object sender, NotifyCollectionChangedEventArgs e) {
@@ -208,40 +229,96 @@ namespace mbts.ui.controls {
_target.InsertRange(e.NewStartingIndex + offset, e.NewItems);
break;
case NotifyCollectionChangedAction.Reset:
var c = (ObservableCollection<T>)sender;
_remove_collection_items((c, c), out int starting_index, out int _);
_add_items(c, starting_index);
var wrapper = sender as NotifierCollectionWrapper;
_remove_collection_items(wrapper, out int starting_index, out int _);
_add_items(wrapper.collection, starting_index);
foreach (T item in wrapper)
_target.Insert(starting_index++, item);
break;
}
}
void _add_items(IList items, int index) {
void _add_items(IEnumerable items, int index) {
foreach (T item in items)
_target.Insert(index++, item);
}
void _remove_collection_items((IReadOnlyCollection<T> items, INotifyCollectionChanged notifier) collection, out int starting_index, out int collection_index) {
void _remove_collection_items(NotifierCollectionWrapper wrapper, out int starting_index, out int collection_index, bool disconnect = false) =>
_remove_collection_items(wrapper.tuple, out starting_index, out collection_index, disconnect);
void _remove_collection_items((IReadOnlyCollection<T> items, INotifyCollectionChanged notifier) collection, out int starting_index, out int collection_index, bool disconnect = false) {
int ex_count = 0;
int i = 0;
for (; i < _collections.Count; i++) {
if (_collections[i].items == collection.items)
if (_collections[i].collection == collection.items) {
if (disconnect)
_collections[i].CollectionChanged -= _on_collection_changed;
break;
else ex_count += _collections[i].items.Count;
} else ex_count += _collections[i].Count;
}
starting_index = ex_count;
collection_index = i;
for (++i; i < _collections.Count; i++)
ex_count += _collections[i].items.Count;
ex_count += _collections[i].Count;
_target.RemoveRange(starting_index, _target.Count - ex_count);
}
int _get_collection_start(object sender) =>
_collections.TakeWhile(c => c.items != sender).Sum(c => c.items.Count);
_collections.TakeWhile(c => c != sender).Sum(c => c.Count);
void _check_disposed() {
if (_is_disposed)
throw new ObjectDisposedException("ObservableCollectionAggregator");
}
class NotifierCollectionWrapper : IReadOnlyCollection<T>, IList, INotifyCollectionChanged {
public NotifierCollectionWrapper((IReadOnlyCollection<T> collection, INotifyCollectionChanged notifier) tuple)
: this(tuple.collection, tuple.notifier) { }
public NotifierCollectionWrapper(IReadOnlyCollection<T> collection, INotifyCollectionChanged notifier) {
this.collection = collection;
this.notifier = notifier;
notifier.CollectionChanged += _on_collection_changed;
}
public object this[int index] {
get => throw new NotImplementedException();
set => throw new NotSupportedException();
}
public IReadOnlyCollection<T> collection { get; }
public INotifyCollectionChanged notifier { get; }
public (IReadOnlyCollection<T> collection, INotifyCollectionChanged notifier) tuple => (collection, notifier);
public int Count => collection.Count;
public bool IsReadOnly => true;
public bool IsFixedSize => false;
public object SyncRoot => null;
public bool IsSynchronized => false;
public event NotifyCollectionChangedEventHandler CollectionChanged;
public int Add(object value) => throw new NotSupportedException();
public void Clear() => throw new NotSupportedException();
public bool Contains(object value) => collection.Contains((T)value);
public void CopyTo(Array array, int index) {
foreach (var v in collection)
array.SetValue(v, index++);
}
public IEnumerator<T> GetEnumerator() => collection.GetEnumerator();
public int IndexOf(object value) => collection.indexOf((T)value);
public void Insert(int index, object value) => throw new NotSupportedException();
public void Remove(object value) => throw new NotSupportedException();
public void RemoveAt(int index) => throw new NotSupportedException();
IEnumerator IEnumerable.GetEnumerator() => collection.GetEnumerator();
void _on_collection_changed(object sender, NotifyCollectionChangedEventArgs e) =>
CollectionChanged?.Invoke(this, e);
}
}
}
@@ -7,7 +7,7 @@ namespace mbts.ui {
if (style == null) {
if (!allow_null)
throw new ArgumentException(nameof(style));
} else if (style.TargetType != expected)
} else if (!style.TargetType.IsAssignableFrom(expected))
throw WPFExceptionBuilder.style_target_type(expected, style.TargetType);
}
@@ -1,65 +0,0 @@
using System;
namespace mbts.ui.utility.text {
/// <summary>
/// Contains a font family, size, and foreground (uint).
/// <para/> NOTE: does not account for font weight, or typeface.
/// </summary>
struct FontParams : IEquatable<FontParams> {
public readonly FontFamily font_family;
public readonly double font_size;
public readonly uint foreground;
public FontParams(FontFamily font_family, double font_size, uint foreground) {
this.font_family = font_family;
this.font_size = font_size;
this.foreground = foreground;
}
public bool Equals(FontParams other) =>
other.font_family == font_family &&
other.font_size == font_size &&
other.foreground == foreground;
public override bool Equals(object obj) =>
obj is FontParams fsk && Equals(fsk);
public override int GetHashCode() =>
font_family.GetHashCode() ^
font_size.GetHashCode() ^
foreground.GetHashCode();
public static bool operator ==(FontParams x, FontParams y) =>
x.Equals(y);
public static bool operator !=(FontParams x, FontParams y) =>
!x.Equals(y);
}
struct FontParamsKey<T> where T : struct, IEquatable<T> {
public readonly T key;
public readonly FontParams font_params;
public FontParamsKey(T key, FontParams font_params) {
this.key = key;
this.font_params = font_params;
}
public bool Equals(FontParamsKey<T> other) =>
other.key.Equals(key) &&
other.font_params == font_params;
public override bool Equals(object obj) =>
obj is FontParamsKey<T> fpk && Equals(fpk);
public override int GetHashCode() =>
key.GetHashCode() ^
font_params.GetHashCode();
public static bool operator ==(FontParamsKey<T> x, FontParamsKey<T> y) =>
x.Equals(y);
public static bool operator !=(FontParamsKey<T> x, FontParamsKey<T> y) =>
!x.Equals(y);
}
}
@@ -2,6 +2,12 @@
namespace mbts {
public static class DoubleHelper {
public static bool AnyNaN(params double[] values) {
for (int i = 0; i < values.Length; i++)
if (double.IsNaN(values[i]))
return true;
return false;
}
public static bool IsNaNOrInfinity(double d) => double.IsNaN(d) || double.IsInfinity(d);
public static bool IsNaNOrInfinityOr(double d, params double[] values) => IsNaNOrInfinity(d) || values.Contains(d);
public static bool IsOneOf(double d, params double[] values) => values.Contains(d);
@@ -1,4 +1,5 @@
using bts.shell.pnl;
using bts.shell.orderbook;
using bts.shell.pnl;
using bts.utils;
using mbts.ui.settings;
using System;
@@ -15,6 +16,7 @@ namespace bts.settings {
// determine the various windows to which such rules apply.
interface IFillsGridSettingsMigrationRule : ISettingsMigrationRule<GridSettings> { }
interface IMarketViewGridSettingsMigrationRule : ISettingsMigrationRule<GridSettings> { }
interface IOrderBookGridSettingsMigrationRule : ISettingsMigrationRule<OrderBookGridSettings> { }
interface IPAndLGridSettingsMigrationRule : ISettingsMigrationRule<GridSettings> { }
interface IPNLSettingsMigrationRule : ISettingsMigrationRule<PNLSettings> { }
interface ISyntheticManagementMigrationRule : ISettingsMigrationRule<GridSettings> { }
@@ -1,5 +1,6 @@
using bts;
using bts.settings;
using bts.shell.orderbook;
using bts.shell.pnl;
using bts.utils;
using mbts.spark;
@@ -28,6 +29,9 @@ namespace mbts.persist {
_pandl_propi = ImmutableProperty.create<UserSettings, GridSettings>(us => us.pnl_grid, (us, gs) => us.with(provided_pnl_gridsettings: gs)),
_synth_propi = ImmutableProperty.create<UserSettings, GridSettings>(us => us.synthetic_management_grid, (us, gs) => us.with(provided_synthetic_management_gridsettings: gs));
static readonly IImmutableProperty<UserSettings, OrderBookGridSettings>
_orderbook_propi = ImmutableProperty.create<UserSettings, OrderBookGridSettings>(us => us.order_book_grid, (us, gs) => us.with(order_book_grid: gs));
static readonly IImmutableProperty<UserSettings, PNLSettings>
_pnl_propi = ImmutableProperty.create<UserSettings, PNLSettings>(us => us.pnl, (us, pnl) => us.with(pnl: pnl));
@@ -35,6 +39,7 @@ namespace mbts.persist {
readonly BehaviorSubject<UserSettings> _default_settings_subject = new BehaviorSubject<UserSettings>(UserSettings.factory_defaults);
readonly IEnumerable<IFillsGridSettingsMigrationRule> _fills_migration_rules;
readonly IEnumerable<IMarketViewGridSettingsMigrationRule> _mview_migration_rules;
readonly IEnumerable<IOrderBookGridSettingsMigrationRule> _orderbook_migration_rules;
readonly IEnumerable<IPAndLGridSettingsMigrationRule> _pandl_migration_rules;
readonly IEnumerable<IPNLSettingsMigrationRule> _pnl_migration_rules;
readonly IEnumerable<ISyntheticManagementMigrationRule> _synth_migration_rules;
@@ -47,6 +52,7 @@ namespace mbts.persist {
IUserPersist persist,
[ImportMany]IEnumerable<IFillsGridSettingsMigrationRule> fills_migration_rules,
[ImportMany]IEnumerable<IMarketViewGridSettingsMigrationRule> mview_migration_rules,
[ImportMany]IEnumerable<IOrderBookGridSettingsMigrationRule> orderbook_migration_rules,
[ImportMany]IEnumerable<IPAndLGridSettingsMigrationRule> pandl_migration_rules,
[ImportMany]IEnumerable<IPNLSettingsMigrationRule> pnl_migration_rules,
[ImportMany]IEnumerable<ISyntheticManagementMigrationRule> synth_migration_rules) {
@@ -54,6 +60,7 @@ namespace mbts.persist {
_persist = persist;
_fills_migration_rules = fills_migration_rules ?? Enumerable.Empty<IFillsGridSettingsMigrationRule>();
_mview_migration_rules = mview_migration_rules ?? Enumerable.Empty<IMarketViewGridSettingsMigrationRule>();
_orderbook_migration_rules = orderbook_migration_rules ?? Enumerable.Empty<IOrderBookGridSettingsMigrationRule>();
_pandl_migration_rules = pandl_migration_rules ?? Enumerable.Empty<IPAndLGridSettingsMigrationRule>();
_pnl_migration_rules = pnl_migration_rules ?? Enumerable.Empty<IPNLSettingsMigrationRule>();
_synth_migration_rules = synth_migration_rules ?? Enumerable.Empty<ISyntheticManagementMigrationRule>();
@@ -130,6 +137,12 @@ namespace mbts.persist {
_mview_migration_rules,
_mview_propi);
public IWindowSettingsManager<OrderBookGridSettings> create_orderbook_settings_manager(OrderBookGridSettings instance_settings) =>
_create_settings_manager(
instance_settings,
_orderbook_migration_rules,
_orderbook_propi);
public IWindowSettingsManager<GridSettings> create_pandl_settings_manager(GridSettings instance_settings) =>
_create_settings_manager(
instance_settings,
+3
View File
@@ -56,6 +56,9 @@ namespace bts {
public static T infer<T, A, B>(object value, Func<A, T> a, Func<B, T> b) =>
OneOf<A, B>.infer(value).Match(a, b);
public static T infer<T, A, B, C>(object value, Func<A, T> a, Func<B, T> b, Func<C, T> c) =>
OneOf<A, B, C>.infer(value).Match(a, b, c);
}
public class OneOf<A, B> {
@@ -1,20 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace mbts.utils {
public class ReverseComparer<T> : IComparer<T> {
private readonly IComparer<T> _wrapped;
public ReverseComparer(IComparer<T> wrapped) {
_wrapped = wrapped;
}
public int Compare(T x, T y) {
// reverse operands
return _wrapped.Compare(y, x);
}
}
}
@@ -23,6 +23,11 @@ namespace mbts.ui.shell {
void clear_error(string property);
}
interface IReadOnlyProperty<T> {
event PropertyChange<T> Changing, Changed;
T value { get; }
}
interface IProperty<T> : IObservable<(T oldval, T newval)> {
event PropertyChange<T> Changing, Changed;
T Value { get; set; }
@@ -36,11 +41,32 @@ namespace mbts.ui.shell {
public delegate void PropertyChange<T>(T oldval, T newval);
static class ValidationViewModelExtensions {
public static IDisposable echo<T>(this IRaiseNotifyPropertyChanged vm, IProperty<T> source_property, string target_property_name) {
source_property.Changed += on_changed;
return Disposable.Create(() => source_property.Changed -= on_changed);
void on_changed(T oldval, T newval) =>
vm.raise_notify_property_changed(target_property_name);
}
public static void assert(this IValidationViewModel vm, bool condition, string property, string message) {
if (condition) vm.clear_error(property);
else vm.set_error(property, message);
}
public static IDisposable subscribe_changed<T>(this IProperty<T> property, IObserver<(T oldval, T newval)> observer) {
property.Changed += on_changed;
return Disposable.Create(() => property.Changed -= on_changed);
void on_changed(T oldval, T newval) =>
observer.OnNext((oldval, newval));
}
public static IDisposable subscribe_changing<T>(this IProperty<T> property, IObserver<(T oldval, T newval)> observer) {
property.Changing += on_changing;
return Disposable.Create(() => property.Changing -= on_changing);
void on_changing(T oldval, T newval) =>
observer.OnNext((oldval, newval));
}
public static IObservable<bool> subscribe_has_errors(this INotifyDataErrorInfo vm) =>
Observable.Create<bool>(o => {
vm.ErrorsChanged += on_errors_changed;
@@ -53,6 +79,17 @@ namespace mbts.ui.shell {
// ensure notifications will be in on the same thread as IProperty<T>.Subscribe (don't use .Select extension)
Observable.Create<T>(o => property.Subscribe(ch => o.OnNext(ch.newval)));
public static IObservable<T> SubscribeValue<T>(this IProperty<T> property, bool start_immediately) =>
start_immediately ?
// ensure notifications will be in on the same thread as IProperty<T>.Subscribe (don't use .Select extension)
Observable.Create<T>(o => {
o.OnNext(property.Value);
return property.Subscribe(ch => o.OnNext(ch.newval));
}) :
SubscribeValue(property);
//public static IDisposable S
public static IProperty<T> block_reentrancy<T>(this IProperty<T> source) =>
new GatedPropertyWrapper<T>(source);
@@ -89,6 +126,7 @@ namespace mbts.ui.shell {
return GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance);
});
}
protected void RaisePropertyChanged([CallerMemberName]string property="") {
if (PropertyChanged != null) {
PropertyChanged(this, new PropertyChangedEventArgs(property));
@@ -22,12 +22,20 @@ namespace bts.proto {
}
public Color color => _color.rgb_color();
public Color argb_color => Color.FromArgb((byte)(MathUtils.within_range(0, 1, _opacity) * 255.0), color.R, color.G, color.B);
// TODO: get rid of this nonsense, all we need is a uint to store color & opacity
// (opacity is just the alpha bits of the color)
public Color argb_color =>
opacity == 1 ?
_color.argb_color() :
Color.FromArgb((byte)(MathUtils.within_range(0, 1, _opacity) * 255.0), color.R, color.G, color.B);
public double opacity => _opacity;
public ColorOpacityProto with(uint? color = null, double? opacity = null) =>
new ColorOpacityProto(color ?? _color, opacity ?? _opacity);
public SolidColorBrush to_argb_brush() =>
new SolidColorBrush(argb_color) { Opacity = _opacity };
public SolidColorBrush to_brush() =>
new SolidColorBrush(_color.rgb_color()) { Opacity = _opacity };
+15 -1
View File
@@ -5,9 +5,23 @@
xmlns:ui="clr-namespace:mbts.ui"
>
<!--Control Base Colors-->
<Color x:Key="Control.SuperLightGrayColor">#F6F6F6</Color>
<Color x:Key="Control.LightGrayColor">#E5E5E5</Color>
<Color x:Key="Control.LightMediumGrayColor">#CFCFCF</Color>
<Color x:Key="Control.MediumGrayColor">#B3B3B3</Color>
<Color x:Key="Control.MediumDarkGrayColor">#ACACAC</Color>
<Color x:Key="Control.AlphaLightGrayColor">#10000000</Color>
<Color x:Key="Control.AlphaLightMediumGrayColor">#24000000</Color>
<Color x:Key="Control.AlphaMediumGrayColor">#3A000000</Color>
<Color x:Key="Control.AlphaMediumGrayColor2">#FF0000</Color>
<Color x:Key="Control.DarkMediumGrayColor">#FF959595</Color>
<Color x:Key="Control.DarkGrayColor">#707070</Color>
<Color x:Key="Text.Prompt.Foreground.Color">#FF999999</Color>
<SolidColorBrush x:Key="Control.AlphaLightGrayBrush" Color="{StaticResource Control.AlphaLightGrayColor}"/>
<SolidColorBrush x:Key="Control.AlphaLightMediumGrayBrush" Color="{StaticResource Control.AlphaLightMediumGrayColor}"/>
<SolidColorBrush x:Key="Control.LightGrayBrush" Color="{StaticResource Control.LightGrayColor}"/>
<SolidColorBrush x:Key="Control.LightMediumGrayBrush" Color="{StaticResource Control.LightMediumGrayColor}"/>
<SolidColorBrush x:Key="Control.MediumGrayBrush" Color="{StaticResource Control.MediumGrayColor}"/>
<SolidColorBrush x:Key="CommonBackground" Color="{ui:BTSColor common_background}" />
<SolidColorBrush x:Key="ControlBackground" Color="{x:Static mbts:BTSColors.common_background}" />
@@ -130,4 +144,4 @@
<SolidColorBrush x:Key="ListBoxItem.Selected.Inactive.Border" Color="{StaticResource ListBoxItem.Selected.Inactive.Border.Color}"/>
<SolidColorBrush x:Key="ListBoxItem.MouseOver.Background" Color="{StaticResource ListBoxItem.MouseOver.Background.Color}"/>
<SolidColorBrush x:Key="ListBoxItem.MouseOver.Border" Color="{StaticResource ListBoxItem.MouseOver.Border.Color}"/>
</ResourceDictionary>
</ResourceDictionary>
@@ -1,78 +1,102 @@
<c:PopupControl
x:Class="mbts.ui.controls.ActionMenu"
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:u="clr-namespace:mbts.ui"
xmlns:c="clr-namespace:mbts.ui.controls"
Margin="1"
Background="Transparent"
BorderBrush="Transparent"
Padding="2"
PopupPlacement="Bottom"
ShowExpanderArrow="False"
Cursor="Hand"
xmlns:cv="clr-namespace:mbts.ui.converters"
>
<c:PopupControl.Resources>
<CollectionViewSource x:Key="ItemsSourceView" Source="{Binding ItemsSource, RelativeSource={RelativeSource AncestorType=c:ActionMenu}}">
<CollectionViewSource.GroupDescriptions>
<PropertyGroupDescription PropertyName="group"/>
</CollectionViewSource.GroupDescriptions>
</CollectionViewSource>
</c:PopupControl.Resources>
<c:PopupControl.Header>
<Border Width="16">
<SolidColorBrush x:Key="SeparatorFill" Color="{DynamicResource Control.AlphaMediumGrayColor}"/>
<ControlTemplate x:Key="ActionMenuTemplate" TargetType="c:ActionMenu">
<Border
HorizontalAlignment="{TemplateBinding HorizontalAlignment}"
VerticalAlignment="{TemplateBinding VerticalAlignment}"
Width="{TemplateBinding Width}"
Height="{TemplateBinding Height}"
>
<u:StateManager.Groups>
<u:StateGroupsManager AttachToMembers="True">
<u:StateDefinitionCollection>
<u:MouseStateDefinition MouseSource="{Binding Group.Target, RelativeSource={RelativeSource Self}}"/>
</u:StateDefinitionCollection>
</u:StateGroupsManager>
</u:StateManager.Groups>
<VisualStateManager.VisualStateGroups>
<VisualStateGroup Name="MouseStates">
<VisualState Name="Normal"/>
<VisualState Name="MouseOver" Storyboard="{DynamicResource Button.MouseOver.Storyboard}"/>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
<Grid>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<Rectangle Grid.Row="0" Fill="Black" Height="2" HorizontalAlignment="Stretch"/>
<Rectangle Grid.Row="1" Fill="Black" Height="2" Margin="0,4,0,0" HorizontalAlignment="Stretch"/>
<Rectangle Grid.Row="2" Fill="Black" Height="2" Margin="0,4,0,0" HorizontalAlignment="Stretch"/>
<Button
x:Name="button"
Content="{TemplateBinding Header}"
ContentTemplate="{TemplateBinding HeaderTemplate}"
ContentTemplateSelector="{TemplateBinding HeaderTemplateSelector}"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
Padding="2.5,2"
Style="{DynamicResource SparkToolBarButton}"
>
<Button.Background>
<SolidColorBrush/>
</Button.Background>
</Button>
<Popup c:PopupViewController.ButtonSource="{Binding ElementName=button}" Placement="Bottom" PlacementTarget="{Binding ElementName=button}" StaysOpen="False">
<Border Background="{DynamicResource Control.LightGrayBrush}" BorderThickness="1">
<Border.BorderBrush>
<SolidColorBrush Color="{DynamicResource Control.DarkMediumGrayColor}"/>
</Border.BorderBrush>
<ItemsPresenter/>
</Border>
</Popup>
</Grid>
</Border>
</c:PopupControl.Header>
<Menu ItemsSource="{Binding Source={StaticResource ItemsSourceView}}">
<Menu.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Vertical"/>
</ItemsPanelTemplate>
</Menu.ItemsPanel>
<Menu.GroupStyle>
<GroupStyle>
<GroupStyle.ContainerStyle>
<Style TargetType="GroupItem">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="GroupItem">
<StackPanel>
<ItemsPresenter/>
<Rectangle HorizontalAlignment="Stretch" Height="1" Margin="22,0,0,0" Fill="LightGray"/>
</StackPanel>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</GroupStyle.ContainerStyle>
</GroupStyle>
</Menu.GroupStyle>
<Menu.ItemContainerStyle>
<Style TargetType="MenuItem" BasedOn="{StaticResource {x:Type MenuItem}}">
<!--NOTE: CommandParameter binding must be declared BEFORE Command,
otherwise, CanExecuted is called before the parameter value is realized...I know, it sucks...-->
<Setter Property="CommandParameter" Value="{Binding parameter}"/>
<Setter Property="Command" Value="{Binding command}"/>
<Setter Property="Header" Value="{Binding name}"/>
<Setter Property="HeaderTemplate">
<Setter.Value>
<DataTemplate>
<TextBlock Text="{Binding}" VerticalAlignment="Center"/>
</DataTemplate>
</Setter.Value>
</Setter>
<Setter Property="Icon" Value="{Binding icon, Converter={StaticResource IconConverter}}"/>
<Setter Property="MinWidth" Value="167"/>
</Style>
</Menu.ItemContainerStyle>
</Menu>
</c:PopupControl>
</ControlTemplate>
<DataTemplate x:Key="HamburgerButtonTemplate">
<Image
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
Source="{x:Static u:Icons.HamburgerMenu}"
/>
</DataTemplate>
<Style x:Key="ActionMenuBase" TargetType="c:ActionMenu">
<Setter Property="Background">
<Setter.Value>
<SolidColorBrush/>
</Setter.Value>
</Setter>
<Setter Property="BorderBrush">
<Setter.Value>
<SolidColorBrush/>
</Setter.Value>
</Setter>
<Setter Property="Template" Value="{StaticResource ActionMenuTemplate}"/>
</Style>
<DataTemplate DataType="{x:Type u:MenuItemVM}">
<TextBlock Text="{Binding}" VerticalAlignment="Center"/>
</DataTemplate>
<Style TargetType="c:ActionMenu" BasedOn="{StaticResource ActionMenuBase}">
<Setter Property="Panel.ZIndex" Value="1"/>
<Setter Property="HorizontalAlignment" Value="Right"/>
<Setter Property="VerticalAlignment" Value="Top"/>
<Setter Property="Margin" Value="1"/>
<Setter Property="HeaderTemplate" Value="{StaticResource HamburgerButtonTemplate}"/>
<Setter Property="Width" Value="22"/>
<Setter Property="ItemContainerStyle">
<Setter.Value>
<Style TargetType="MenuItem">
<Setter Property="MinWidth" Value="167"/>
<Setter Property="CommandParameter" Value="{Binding parameter}"/>
<Setter Property="Command" Value="{Binding command}"/>
<Setter Property="Header" Value="{Binding name}"/>
<Setter Property="Icon" Value="{Binding icon, Converter={x:Static cv:ImageSourceIconConverter.Instance}}"/>
</Style>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>
@@ -1,22 +1,15 @@
using System.Collections;
using System.Linq;
using System.Windows;
using System.Windows;
using System.Windows.Controls;
namespace mbts.ui.controls {
/// <summary>
/// Interaction logic for ActionMenu.xaml
/// </summary>
partial class ActionMenu : PopupControl {
public ActionMenu() =>
InitializeComponent();
class ActionMenu : HeaderedItemsControl {
static ActionMenu() =>
DefaultStyleKeyProperty.OverrideMetadata(typeof(ActionMenu), new FrameworkPropertyMetadata(typeof(ActionMenu)));
public static readonly DependencyProperty ItemsSourceProperty =
WPFHelper.create_dp<IEnumerable, ActionMenu>(
nameof(ItemsSource), Enumerable.Empty<IMenuItemVM>());
public IEnumerable ItemsSource {
get => GetValue(ItemsSourceProperty) as IEnumerable;
set => SetValue(ItemsSourceProperty, value);
}
protected override DependencyObject GetContainerForItemOverride() =>
new MenuItem();
}
}
@@ -38,7 +38,7 @@ namespace mbts.ui.controls.adorners {
move_to(e.GetPosition(target));
void move_to(Point pt) =>
mta.RenderTransform = new TranslateTransform(pt.X, pt.Y);
mta.Margin = new Thickness(pt.X, pt.Y, 0, 0);
}
public static IDisposable trace_drag(FrameworkElement target, Visual trace_visual, Size trace_visual_size) {
@@ -55,7 +55,7 @@ namespace mbts.ui.controls.adorners {
move_to(e.GetPosition(target));
void move_to(Point pt) =>
mta.RenderTransform = new TranslateTransform(pt.X, pt.Y);
mta.Margin = new Thickness(pt.X, pt.Y, 0, 0);
}
protected override Visual GetVisualChild(int index) =>
@@ -9,13 +9,14 @@
<SolidColorBrush x:Key="Ellipse.Pressed.Brush" Color="#FF3C77DD"/>
<SolidColorBrush x:Key="Ellipse.Disabled.Brush" Color="Gray"/>
<Style TargetType="c:ExpanderToggleButton" BasedOn="{StaticResource {x:Type ToggleButton}}">
<Setter Property="Background" Value="Transparent"/>
<Setter Property="BorderBrush" Value="Black"/>
<Setter Property="BorderThickness" Value="1"/>
<Setter Property="Padding" Value="3,0,0,0"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ToggleButton">
<Border>
<ControlTemplate TargetType="c:ExpanderToggleButton">
<Border Background="{TemplateBinding Background}">
<Grid Background="Transparent">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
@@ -30,7 +31,15 @@
Stroke="{StaticResource Path.Static.Brush}"
StrokeThickness="2"
VerticalAlignment="Center"
/>
>
<Path.Visibility>
<Binding Path="ShowExpanderArrow" RelativeSource="{RelativeSource TemplatedParent}">
<Binding.Converter>
<cv:BooleanToVisibilityConverter VisibilityForFalse="Hidden" VisibilityForTrue="Visible"/>
</Binding.Converter>
</Binding>
</Path.Visibility>
</Path>
<ContentPresenter
Grid.Column="1"
ContentSource="{TemplateBinding Content}"
@@ -1,12 +1,28 @@
using System.Windows;
using System.Windows.Controls.Primitives;
using System.Windows.Input;
namespace mbts.ui.controls {
/// <summary>
/// Interaction logic for ExpanderToggleButton.xaml
/// </summary>
class ExpanderToggleButton : ToggleButton {
public static readonly RoutedCommand ToggleCommand = new RoutedCommand();
public static readonly ExecutedRoutedEventHandler ToggleExecuted = (s, e) => {
var bn = s as ToggleButton ?? e.Parameter as ToggleButton;
if (bn != null)
bn.SetCurrentValue(IsCheckedProperty, !bn.IsChecked.GetValueOrDefault());
};
static ExpanderToggleButton() =>
DefaultStyleKeyProperty.OverrideMetadata(typeof(ExpanderToggleButton), new FrameworkPropertyMetadata(typeof(ExpanderToggleButton)));
public static readonly DependencyProperty ShowExpanderArrowProperty =
WPFHelper.create_dp<bool, ExpanderToggleButton>(nameof(ShowExpanderArrow), true);
public bool ShowExpanderArrow {
get => (bool)GetValue(ShowExpanderArrowProperty);
set => SetValue(ShowExpanderArrowProperty, value);
}
}
}
@@ -1,4 +1,5 @@
using mbts.spark;
using bts.collections;
using mbts.spark;
using mbts.spark.utilities;
using ProtoBuf;
using System.Collections.Generic;
@@ -23,6 +24,7 @@ namespace mbts.ui.controls.color_picker {
yield return factory_order_colors_palette(order_factory);
yield return application_palette();
yield return bts_palette();
yield return system_palette();
yield return blank_palette();
}
@@ -109,6 +111,14 @@ namespace mbts.ui.controls.color_picker {
}
};
public static ColorPalette system_palette() =>
new ColorPalette {
group = "System",
colors = ColorUtils.SystemColors.WrapReadOnlySelector(
nc => new NamedColor(nc.value, nc.name))
.ToList()
};
public static ColorPalette blank_palette() =>
new ColorPalette {
group = "Custom",
@@ -1,11 +1,132 @@
using System;
using bts.collections;
using bts.utils;
using mbts.ui.utilities;
using mbts.utils;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Runtime.InteropServices;
using Color = System.Windows.Media.Color;
using System.Windows.Media;
namespace mbts.ui.controls.color_picker {
public static class ColorUtils {
static class ColorUtils {
// (L)ight -> (H)ue comparer (hierarchical)
public static readonly IComparer<Color> LHComparer =
new ComparerBuilder<Color> {
ReverseComparer<double>.Default.wrap<double, Color>(brightness),
Comparer<double>.Default.wrap<double, Color>(hue)
}.to_comparer();
static readonly Named<Color>[]
_system_colors,
// 'pure' means no grays
_pure_web_colors,
_gray_web_colors,
_all_web_colors,
/// The 'grid' arrays below have the same colors as the arrays above, but are sorted differently to represent a grid structure:
/// - 140 web colors are split into 7 rows (20 items each),
/// - each row represents a 'hue' bucket
/// - the items within each row are sorted by lightness
/// Note: these 'grid-sorted' arrays serve as the [default]ItemsSource for the <see cref="TabularColorPicker"/>, which presents the items in a UniformGrid.
_pure_web_colors_grid,
_all_web_colors_grid;
static ColorUtils() {
var named_color_lh_comp = LHComparer.wrap<Color, Named<Color>>(nc => nc.value);
_system_colors = Array.ConvertAll(
typeof(Colors).GetProperties(BindingFlags.Static | BindingFlags.Public),
p => new Named<Color>(
DisplayNameHelper.camel_to_title_case(p.Name),
(Color)p.GetValue(null)));
Array.Sort(_system_colors, named_color_lh_comp);
_all_web_colors = Array.ConvertAll(
typeof(System.Drawing.Color)
.GetProperties(BindingFlags.Public | BindingFlags.Static),
p => {
var c = System.Drawing.Color.FromName(p.Name);
return new Named<Color>(p.Name, Color.FromArgb(c.A, c.R, c.G, c.B));
});
Array.Sort(_all_web_colors, named_color_lh_comp);
var all_web_colors_grid = new Named<Color>[_all_web_colors.Length - 1];
int adj = 0;
for (int i = 0; i < _all_web_colors.Length; i++) {
if (_all_web_colors[i].value.A == 0) {
adj++;
continue;
}
all_web_colors_grid[i - adj] = _all_web_colors[i];
}
SortColorGrid(ref all_web_colors_grid, nc => nc.value);
_all_web_colors_grid = all_web_colors_grid;
// break up into pure/gray
int num_gray = all_web_colors_grid.Count(nc => nc.value.is_gray());
_pure_web_colors = new Named<Color>[all_web_colors_grid.Length - num_gray];
_gray_web_colors = new Named<Color>[num_gray];
int gpos = 0;
int egpos = 0;
for (int i = 0; i < all_web_colors_grid.Length; i++) {
var nc = all_web_colors_grid[i];
if (nc.value.is_gray()) {
_gray_web_colors[gpos++] = nc;
} else {
_pure_web_colors[egpos++] = nc;
}
}
// gray
Array.Sort(_gray_web_colors, Comparer<double>.Default.wrap<double, Named<Color>>(nc => nc.value.brightness()));
// pure
var pure_web_colors_grid = _pure_web_colors.ToArray();
SortColorGrid(ref pure_web_colors_grid, nc => nc.value);
_pure_web_colors_grid = pure_web_colors_grid;
}
public static IReadOnlyList<Named<Color>> SystemColors =>
_system_colors;
public static IReadOnlyList<NamedColorVM> SystemColorVMs =>
_system_colors.WrapSelectorRO(_mk_readonly_vm);
public static IReadOnlyList<Named<Color>> WebColors =>
_all_web_colors;
public static IReadOnlyList<NamedColorVM> WebColorVMs =>
_all_web_colors.WrapSelectorRO(_mk_readonly_vm);
public static IReadOnlyList<NamedColorVM> GrayWebColorVMs =>
_gray_web_colors.WrapSelectorRO(_mk_readonly_vm);
public static IReadOnlyList<NamedColorVM> PureWebColorVMs =>
_pure_web_colors_grid.WrapSelectorRO(_mk_readonly_vm);
public static IReadOnlyList<NamedColorVM> WebColorVMGrid =>
_all_web_colors_grid.WrapSelectorRO(_mk_readonly_vm);
public static void SortColorGrid<T>(ref T[] color_items, Func<T, Color> get_color, int column_count = 20) {
// Sorting colors is a lot harder than one would think, esp when the distribution of rgb values is non-linear.
// The questions one must ask are "how many shade groups should there be, and futhermore, how is a shade defined?"
// The logic employed below first breaks the colors up into 6 groups of hues (each occupying a range of 60/360 degrees of hue).
// A somewhat aribitrary offset of '10' is applied to achieve 'better looking' hue groups.
// -------------------------------------------------------------------
/// 1) sort by hue buckets (divide the 360 degrees of hue into 6 slices)
var hue_comp = Comparer<int>.Default.wrap<int, T>(ci => {
double raw_hue = get_color(ci).hue();
double adj_hue = raw_hue + 10;
if (adj_hue > 360) {
adj_hue -= 360;
}
int hue_bucket = (int)Math.Ceiling(adj_hue / 60.0);
return hue_bucket;
});
Array.Sort(color_items, hue_comp);
/// 2) break up the array into rows, and sort each row by lightness
var lightness_comp = Comparer<double>.Default.wrap<double, T>(ci => get_color(ci).brightness()).reverse();
for (int i = 0; i < color_items.Length; i+=column_count) {
int row_size = Math.Min(column_count, color_items.Length - i);
Array.Sort(color_items, i, row_size, lightness_comp);
}
}
[DllImport("shlwapi.dll")]
public static extern int ColorHLSToRGB(int H, int L, int S);
@@ -15,7 +136,6 @@ namespace mbts.ui.controls.color_picker {
return Color.FromArgb(255, bytes[0], bytes[1], bytes[2]);
}
public static string ToHexString(this Color c) =>
"#" + c.R.ToString("X2") + c.G.ToString("X2") + c.B.ToString("X2");
@@ -170,17 +290,7 @@ namespace mbts.ui.controls.color_picker {
return Color.FromRgb((byte)(from.R + rdiff), (byte)(from.G + gdiff), (byte)(from.B + bdiff));
}
// https://stackoverflow.com/questions/5091455/web-color-list-in-c-sharp-application
public static List<Color> GetWebColors() {
Type colors = typeof(System.Drawing.Color);
PropertyInfo[] colorInfo = colors.GetProperties(BindingFlags.Public |
BindingFlags.Static);
List<Color> list = new List<Color>();
foreach (PropertyInfo info in colorInfo) {
var c = System.Drawing.Color.FromName(info.Name);
list.Add(Color.FromArgb(c.A, c.R, c.G, c.B));
}
return list;
}
static NamedColorVM _mk_readonly_vm(Named<Color> nc) =>
new NamedColorVM(new NamedColor(nc.value, nc.name), false);
}
}
@@ -5,6 +5,7 @@
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:u="clr-namespace:mbts.ui"
xmlns:c="clr-namespace:mbts.ui.controls"
xmlns:cv="clr-namespace:mbts.ui.converters"
xmlns:cp="clr-namespace:mbts.ui.controls.color_picker"
>
<Control.Resources>
@@ -74,7 +75,29 @@
</Control.Resources>
<Control.Template>
<ControlTemplate TargetType="cp:ColorPickerControl">
<Grid>
<Grid DataContext="{Binding Model, RelativeSource={RelativeSource TemplatedParent}}">
<u:StateManager.VisualState>
<Binding Path="ShowControls" RelativeSource="{RelativeSource TemplatedParent}">
<Binding.Converter>
<cv:BooleanToObjectConverter ValueForFalse="HideControls" ValueForTrue="ShowControls"/>
</Binding.Converter>
</Binding>
</u:StateManager.VisualState>
<VisualStateManager.VisualStateGroups>
<VisualStateGroup Name="ControlStates">
<VisualState Name="ShowControls"/>
<VisualState Name="HideControls">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="PaletteTabsGrid" Storyboard.TargetProperty="Visibility">
<DiscreteObjectKeyFrame KeyTime="0" Value="{x:Static Visibility.Collapsed}"/>
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="SlidersGrid" Storyboard.TargetProperty="Visibility">
<DiscreteObjectKeyFrame KeyTime="0" Value="{x:Static Visibility.Collapsed}"/>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="326" />
<ColumnDefinition Width="*" />
@@ -188,7 +211,7 @@
</TabItem>
</TabControl>
</Grid>
<Grid Grid.Column="1" Margin="{StaticResource PanelMargin}" HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
<Grid x:Name="SlidersGrid" Grid.Column="1" Margin="{StaticResource PanelMargin}" HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="15" />
<ColumnDefinition Width="*" MinWidth="120" />
@@ -299,7 +322,7 @@
/>
</Grid>
<Grid Grid.Row="1" Grid.ColumnSpan="3" Margin="3">
<Grid x:Name="PaletteTabsGrid" Grid.Row="1" Grid.ColumnSpan="3" Margin="3">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*"/>
@@ -311,7 +334,12 @@
BorderThickness="2"
Background="{Binding result, Converter={StaticResource ColorToBrushConverter}}"
/>
<TabControl ItemsSource="{Binding palettes}" Grid.Column="1" HorizontalAlignment="Stretch" ItemContainerStyle="{StaticResource PaletteTabItem}"/>
<TabControl
ItemsSource="{Binding palettes}"
Grid.Column="1"
HorizontalAlignment="Stretch"
ItemContainerStyle="{StaticResource PaletteTabItem}"
/>
</Grid>
</Grid>
</ControlTemplate>
@@ -24,7 +24,6 @@ namespace mbts.ui.controls.color_picker {
partial class ColorPickerControl : Control {
const string _max_saturation_color_key = "MaxSaturationColor";
readonly PropertyUpdateGate _saturation_border_gate = new PropertyUpdateGate();
readonly ColorPickerVM _color_vm = new ColorPickerVM();
DataGrid _named_colors_grid;
Border _saturation_border;
@@ -34,7 +33,6 @@ namespace mbts.ui.controls.color_picker {
public ColorPickerControl() {
InitializeComponent();
DataContext = _color_vm;
CommandManager.AddExecutedHandler(this, executed_handler);
CommandManager.AddCanExecuteHandler(this, can_execute_handler);
@@ -50,10 +48,10 @@ namespace mbts.ui.controls.color_picker {
_named_colors_grid.CommitEdit();
} else if (e.Parameter is NamedColorVM vm) {
if (e.Command == ColorPickerSwatch.AssignColorCommand)
vm.assign_color(_color_vm.result);
vm.assign_color(Model.result);
else if (e.Command == ColorPickerSwatch.NameColorCommand) {
_named_colors_tab.IsSelected = true;
_color_vm.selected_named_color = vm;
Model.selected_named_color = vm;
_named_colors_grid.ScrollIntoView(vm);
_named_colors_grid.CurrentCell = new DataGridCellInfo(vm, _named_colors_grid.Columns[0]);
}
@@ -64,9 +62,26 @@ namespace mbts.ui.controls.color_picker {
e.CanExecute = true;
}
public event Action<Color> SelectedColorChanged;
public ColorPickerVM Model { get; } = new ColorPickerVM();
public static readonly DependencyProperty ShowControlsProperty =
WPFHelper.create_dp<bool, ColorPickerControl>(nameof(ShowControls), true);
public bool ShowControls {
get => (bool)GetValue(ShowControlsProperty);
set => SetValue(ShowControlsProperty, value);
}
#region SelectedColor
public static readonly DependencyProperty SelectedColorProperty =
WPFHelper.create_dp<Color, ColorPickerControl>(nameof(SelectedColor), (cp, val) => cp._color_vm.result = val);
WPFHelper.create_dp<Color, ColorPickerControl>(
nameof(SelectedColor),
(cp, val) => {
cp.Model.result = val;
cp.SelectedColorChanged?.Invoke(val);
});
public Color SelectedColor { get => (Color)GetValue(SelectedColorProperty); set => SetValue(SelectedColorProperty, value); }
@@ -95,17 +110,17 @@ namespace mbts.ui.controls.color_picker {
// b) destroy and recreate color_vm without breaking TabControl bindings, and ICollectionView source dependencies
void _on_palettes_collection_changed(object sender, NotifyCollectionChangedEventArgs e) {
switch (e.Action) {
case NotifyCollectionChangedAction.Add: _color_vm.palettes.InsertRange(e.NewStartingIndex, e.NewItems); break;
case NotifyCollectionChangedAction.Remove: _color_vm.palettes.RemoveRange(e.OldStartingIndex, e.OldItems.Count); break;
case NotifyCollectionChangedAction.Move: _color_vm.palettes.Move(e.OldStartingIndex, e.NewStartingIndex); break;
case NotifyCollectionChangedAction.Add: Model.palettes.InsertRange(e.NewStartingIndex, e.NewItems); break;
case NotifyCollectionChangedAction.Remove: Model.palettes.RemoveRange(e.OldStartingIndex, e.OldItems.Count); break;
case NotifyCollectionChangedAction.Move: Model.palettes.Move(e.OldStartingIndex, e.NewStartingIndex); break;
case NotifyCollectionChangedAction.Replace:
_color_vm.palettes.RemoveRange(e.OldStartingIndex, e.OldItems.Count);
_color_vm.palettes.InsertRange(e.NewStartingIndex, e.NewItems);
Model.palettes.RemoveRange(e.OldStartingIndex, e.OldItems.Count);
Model.palettes.InsertRange(e.NewStartingIndex, e.NewItems);
break;
case NotifyCollectionChangedAction.Reset:
_color_vm.palettes.Clear();
Model.palettes.Clear();
if (sender is IEnumerable<ColorPaletteVM> items)
_color_vm.palettes.AddRange(items);
Model.palettes.AddRange(items);
break;
}
}
@@ -119,17 +134,17 @@ namespace mbts.ui.controls.color_picker {
_saturation_border = this.assert_template_child<Border>("_saturation_border");
_sample_image = this.assert_template_child<Image>("_sample_image");
// TabItems do not inherit DataContext properly from its visual parent, which breaks certain bindings
_named_colors_grid.SetBinding(DataGrid.ItemsSourceProperty, new Binding(nameof(ColorPickerVM.all_colors_view)) { Source = _color_vm });
_named_colors_grid.SetBinding(DataGrid.SelectedItemProperty, new Binding(nameof(ColorPickerVM.selected_named_color)) { Source = _color_vm });
_color_vm.PropertyChanged += color_vm_property_changed;
_color_vm.result = SelectedColor;
_named_colors_grid.SetBinding(DataGrid.ItemsSourceProperty, new Binding(nameof(ColorPickerVM.all_colors_view)) { Source = Model });
_named_colors_grid.SetBinding(DataGrid.SelectedItemProperty, new Binding(nameof(ColorPickerVM.selected_named_color)) { Source = Model });
Model.PropertyChanged += color_vm_property_changed;
Model.result = SelectedColor;
IDisposable mouse_move_hook = null;
_saturation_border.MouseDown += saturation_border_mouse_down;
_saturation_border.MouseUp += saturation_border_mouse_up;
_template_hooks = Disposable.Create(() => {
_named_colors_grid.clear_bindings(DataGrid.ItemsSourceProperty, DataGrid.SelectedItemProperty);
_color_vm.PropertyChanged -= color_vm_property_changed;
Model.PropertyChanged -= color_vm_property_changed;
_saturation_border.MouseDown -= saturation_border_mouse_down;
_saturation_border.MouseUp -= saturation_border_mouse_up;
Disposable.dispose(ref mouse_move_hook);
@@ -138,7 +153,7 @@ namespace mbts.ui.controls.color_picker {
void color_vm_property_changed(object sender, PropertyChangedEventArgs e) {
switch (e.PropertyName) {
case nameof(ColorPickerVM.max_saturation): _update_saturation_img(); break;
case nameof(ColorPickerVM.result): SelectedColor = _color_vm.result; break;
case nameof(ColorPickerVM.result): SelectedColor = Model.result; break;
}
}
@@ -162,7 +177,7 @@ namespace mbts.ui.controls.color_picker {
var left_adj = ColorUtils.adjust_color(tr, Colors.White, left);
var result = ColorUtils.adjust_color(left_adj, Colors.Black, down);
using (_saturation_border_gate.update())
_color_vm.result = result;
Model.result = result;
}
}
@@ -173,7 +188,7 @@ namespace mbts.ui.controls.color_picker {
void _update_saturation_img() =>
_saturation_border_gate.try_update(() =>
Resources[_max_saturation_color_key] = _color_vm.max_saturation);
Resources[_max_saturation_color_key] = Model.max_saturation);
void _sample_image_click(BitmapSource img, Point pos) {
// https://social.msdn.microsoft.com/Forums/vstudio/en-US/82a5731e-e201-4aaf-8d4b-062b138338fe/getting-pixel-information-from-a-bitmapimage?forum=wpf
@@ -188,9 +203,9 @@ namespace mbts.ui.controls.color_picker {
byte red = pixels[index];
byte green = pixels[index + 1];
byte blue = pixels[index + 2];
byte alpha = pixels[index + 3];
byte alpha = Model.alpha;
var color = Color.FromArgb(alpha, blue, green, red);
_color_vm.result = color;
Model.result = color;
}
void _sample_image_mouse_down(object sender, MouseButtonEventArgs e) {
@@ -0,0 +1,26 @@
<Window
x:Class="mbts.ui.controls.color_picker.ColorPickerDialog"
x:ClassModifier="internal"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:u="clr-namespace:mbts.ui"
xmlns:c="clr-namespace:mbts.ui.controls"
xmlns:cv="clr-namespace:mbts.ui.converters"
xmlns:cp="clr-namespace:mbts.ui.controls.color_picker"
u:WindowStyle.SizeToContent="WidthAndHeight"
u:WindowStyle.AllowResize="False"
Style="{StaticResource BTSDialog}"
>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<cp:ColorPickerControl x:Name="color_picker"/>
<c:DialogSkirtOKCancel
x:Name="dialog_skirt"
Grid.Row="1"
BorderThickness="0"
/>
</Grid>
</Window>
@@ -0,0 +1,47 @@
using bts.utils;
using mbts.spark.utilities;
using mbts.ui.extensions;
using mbts.ui.utility;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Globalization;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
namespace mbts.ui.controls.color_picker {
/// <summary>
/// Interaction logic for ColorPickerControl.xaml
/// </summary>
partial class ColorPickerDialog : Window {
public ColorPickerDialog() =>
InitializeComponent();
public static Color pick_color_modal(Window owner = null, Color? init = null, ObservableCollection<ColorPaletteVM> palettes = null) {
var result = Colors.Transparent;
var cpd = new ColorPickerDialog {
WindowStartupLocation = WindowStartupLocation.CenterOwner
};
if (palettes != null) {
cpd.color_picker.Palettes = palettes;
cpd.color_picker.ShowControls = true;
}
if (init.HasValue)
cpd.color_picker.SelectedColor = init.Value;
cpd.dialog_skirt.OKCommand = new DelegateCommand(() => {
result = cpd.color_picker.SelectedColor;
});
cpd.Owner = owner;
cpd.ShowDialog();
return result;
}
}
}
@@ -0,0 +1,115 @@
<UserControl
x:Class="mbts.ui.controls.color_picker.ColorPickerSuite"
x:ClassModifier="internal"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:u="clr-namespace:mbts.ui"
xmlns:c="clr-namespace:mbts.ui.controls"
xmlns:cv="clr-namespace:mbts.ui.converters"
xmlns:cp="clr-namespace:mbts.ui.controls.color_picker"
xmlns:sys="clr-namespace:System;assembly=mscorlib"
FocusVisualStyle="{x:Null}"
Width="437"
>
<UserControl.Resources>
<!--<sys:Byte x:Key="LowAlpha">0x4C</sys:Byte>-->
<cv:MultiValueConverterPipe x:Key="LowAlphaToolTipVisibilityConverter">
<cv:DynamicIsLessThanConverter/>
<cv:BooleanToObjectConverter ValueForFalse="{x:Static Visibility.Collapsed}" ValueForTrue="{x:Static Visibility.Visible}"/>
</cv:MultiValueConverterPipe>
</UserControl.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<c:TabularColorPicker
x:Name="_tabular_color_picker"
SelectedValuePath="color"
ItemsSource="{x:Static cp:ColorUtils.WebColorVMGrid}"
SelectedValue="{Binding Model.selected_named_color, RelativeSource={RelativeSource AncestorType=cp:ColorPickerSuite}, Mode=TwoWay}"
SelectedOpacity="{Binding Model.opacity, RelativeSource={RelativeSource AncestorType=cp:ColorPickerSuite}}"
>
<c:TabularColorPicker.ItemContainerStyle>
<Style TargetType="ListBoxItem" BasedOn="{StaticResource TabularColorPickerItemStyle}">
<!--ToolTips are not in the same VisualTree as their owner, and for some reason the ListBoxItem's Parent is null,
so there isn't a direct binding path from the ToolTip to the ColorPickerSuite.Model.opacity;
the 'Tag' binding is a workaround to make the ColorPickerSuite available to the ToolTip-->
<Setter Property="Tag" Value="{Binding RelativeSource={RelativeSource AncestorType=cp:ColorPickerSuite}}"/>
<Setter Property="ToolTip">
<Setter.Value>
<ToolTip Content="{Binding}">
<ToolTip.ContentTemplate>
<DataTemplate>
<StackPanel>
<u:BindingProxy x:Name="LowAlphaProxy" Value="{DynamicResource LowAlpha}"/>
<TextBlock Text="{Binding name, Converter={StaticResource TitleCaseConverter}}" HorizontalAlignment="Left"/>
<TextBlock HorizontalAlignment="Left">
<TextBlock.Visibility>
<MultiBinding Converter="{StaticResource LowAlphaToolTipVisibilityConverter}">
<Binding Path="PlacementTarget.Tag.Model.opacity" RelativeSource="{RelativeSource AncestorType=ToolTip}"/>
<Binding Path="Value" ElementName="LowAlphaProxy"/>
</MultiBinding>
</TextBlock.Visibility>
<Run>The color may be difficult to see because the opacity is low.</Run>
<LineBreak/>
<Run FontStyle="Italic">Hint: Use the slider on the right to adjust the opacity level.</Run>
</TextBlock>
</StackPanel>
</DataTemplate>
</ToolTip.ContentTemplate>
</ToolTip>
</Setter.Value>
</Setter>
</Style>
</c:TabularColorPicker.ItemContainerStyle>
</c:TabularColorPicker>
<Grid Grid.Column="1" Margin="4,0,0,0" Width="45" ToolTip="Opacity">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<TextBlock HorizontalAlignment="Right">
<TextBlock.Text>
<MultiBinding StringFormat="P" Converter="{x:Static cv:ValueConverters.SliderPercentage}">
<Binding Path="Value" ElementName="_opacity_slider"/>
<Binding Path="Minimum" ElementName="_opacity_slider"/>
<Binding Path="Maximum" ElementName="_opacity_slider"/>
</MultiBinding>
</TextBlock.Text>
</TextBlock>
<Slider
x:Name="_opacity_slider"
Grid.Row="1"
HorizontalAlignment="Center"
Orientation="Vertical"
Minimum="0"
Maximum="255"
Value="{Binding Model.opacity, RelativeSource={RelativeSource AncestorType=cp:ColorPickerSuite}}"
/>
</Grid>
<c:PopupControl
Grid.Row="1"
Grid.ColumnSpan="2"
Padding="6"
HorizontalAlignment="Stretch"
HorizontalContentAlignment="Left"
BorderThickness="0"
ShowExpanderArrow="False"
PopupPlacement="Bottom"
ButtonStyle="{StaticResource AddItemButton}"
Header="Custom"
>
<cp:ColorPickerControl
x:Name="_color_picker"
Padding="0,0,6,0"
Palettes="{Binding Model.palettes, RelativeSource={RelativeSource AncestorType=cp:ColorPickerSuite}}"
SelectedColor="{Binding Model.selected_color, RelativeSource={RelativeSource AncestorType=cp:ColorPickerSuite}, Mode=TwoWay}"
/>
</c:PopupControl>
</Grid>
</UserControl>
@@ -0,0 +1,126 @@
using bts.utils;
using mbts.ui.shell;
using mbts.ui.utility;
using System;
using System.Collections.ObjectModel;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
namespace mbts.ui.controls.color_picker {
/// <summary>
/// Interaction logic for ColorPickerControl.xaml
/// </summary>
partial class ColorPickerSuite : UserControl {
public ColorPickerSuite() {
Model = new ColorModel(this);
InitializeComponent();
Model.on_palettes(Palettes);
}
public static readonly DependencyProperty SelectedColorProperty =
WPFHelper.create_dp<Color, ColorPickerSuite>(
nameof(SelectedColor),
FrameworkPropertyMetadataOptions.BindsTwoWayByDefault,
(o, v) => o.Model.selected_color = v);
public Color SelectedColor {
get => (Color)GetValue(SelectedColorProperty);
set => SetValue(SelectedColorProperty, value);
}
public static readonly DependencyProperty PalettesProperty =
WPFHelper.create_dp<ObservableCollection<ColorPaletteVM>, ColorPickerSuite>(
nameof(Palettes),
(o, v) => {
o.Model.on_palettes(v);
});
public ObservableCollection<ColorPaletteVM> Palettes {
get => GetValue(PalettesProperty) as ObservableCollection<ColorPaletteVM>;
set => SetValue(PalettesProperty, value);
}
public ColorModel Model { get; }
public class ColorModel : ViewModelBase {
readonly ColorPickerSuite _owner;
// Note: 'alpha' and 'opacity' and 'A' (when in the context of 'argb') may be used interchangebly in the comments below.
// -----------------------------------
/// There is an important difference between <see cref="selected_color"/> and <see cref="selected_named_color"/>.
/// The latter is bound to the SelectedValue of the <see cref="TabularColorPicker"/> (TCP), which does not have any opacity input controls.
/// while the former is manually to
/// For this reason, TCP must be immune to alpha channel adjustments, made in other controls (<see cref="ColorPickerSuite"/>, and <see cref="ColorPickerControl"/>, both have alpha/opacity sliders).
/// To achieve this affect, when
/// To achieve this affect, the latter's Alpha should always be 255 (100% opacity).
/// In the <see cref="selected_named_color"/> setter, only the RGB values are forwarded to the 'selected_color' property.
/// This 'alpha buffer' works both ways:
/// - it prevents the TCP from changing the selected_color's alpha to 1 everytime a tabular color item is selected.
/// - and it prevents the alpha sliders from changing TCP's SelectedValue everytime they are adjusted.
///
Color
_selected_color,
_selected_named_color;
IDisposable _palettes_hook;
Action _init;
public ColorModel(ColorPickerSuite owner) {
_owner = owner;
_init = () => {
/// This model demuxes color selection changes from 3 different controls, to a single dependency property (<see cref="SelectedColorProperty"/>).
/// Note the 3 controls are: <see cref="ColorPickerControl"/>, <see cref="TabularColorPicker"/>, <see cref="ColorPickerDialog"/>.
/// Each of the aforementioned controls is bound to one or more properties in this class (<see cref="selected_color"/>, <see cref="selected_named_color"/>, <see cref="palettes"/>).
/// In this design scheme, those controls are the binding targets, and this class the binding source.
/// As the 'binding source,' this class must initialize the 'binding targets' with the appropriate properties.
/// Otherwise, their bogus default values will be assigned to this class, and in turn used to update the binding source of the <see cref="SelectedColorProperty"/>.
/// In other words, the viewmodel's prop that is bound to <see cref="SelectedColorProperty"/> would be overwritten with the bogus default value.
/// To eschew this mess, the code below is executed before the other controls are allowed to affect the <see cref="selected_color"/> property.
/// Thus, they are initialized with the selected color provided by the binding source (aka viewmodel) of the <see cref="SelectedColorProperty"/>, rather than the other way around.
_owner.GetBindingExpression(SelectedColorProperty)?.UpdateTarget();
_owner._color_picker.SelectedColorChanged += c => selected_color = c;
};
}
public Color selected_color {
get => _selected_color;
set {
InvokeOnce.invoke(ref _init);
var alpha = _selected_color.A;
if (Set(ref _selected_color, value)) {
_owner.SetCurrentValue(SelectedColorProperty, value);
if (_selected_color.A != alpha)
RaisePropertyChanged(nameof(opacity));
Set(ref _selected_named_color, value.with(255), nameof(selected_named_color));
}
}
}
public Color selected_named_color {
get => _selected_named_color;
set {
InvokeOnce.invoke(ref _init);
if (Set(ref _selected_named_color, value)) {
if (value != null)
selected_color = value.with(opacity);
}
}
}
public byte opacity {
get => _selected_color.A;
set => selected_color = _selected_color.with(value);
}
public ObservableCollection<ColorPaletteVM> palettes { get; } = new ObservableCollection<ColorPaletteVM>();
public void on_palettes(ObservableCollection<ColorPaletteVM> new_palettes) {
Disposable.dispose(ref _palettes_hook);
if (new_palettes != null)
_palettes_hook = ObservableAutoWrapper.connect(new_palettes, palettes);
}
}
}
}
@@ -1,28 +1,24 @@
<ListBox
<c:TabularColorPicker
x:Class="mbts.ui.controls.color_picker.ColorPickerSwatch"
x:ClassModifier="internal"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:c="clr-namespace:mbts.ui.controls"
xmlns:cp="clr-namespace:mbts.ui.controls.color_picker"
Margin="0"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
ScrollViewer.HorizontalScrollBarVisibility="Disabled"
>
<ListBox.Resources>
<FrameworkElement.Resources>
<cp:NamedColorItemToolTipConverter x:Key="ToolTipConverter"/>
</ListBox.Resources>
<ListBox.ItemsPanel>
</FrameworkElement.Resources>
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel Orientation="Horizontal"/>
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
<ListBox.ItemContainerStyle>
<Style TargetType="ListBoxItem" BasedOn="{StaticResource {x:Type ListBoxItem}}">
<Setter Property="Cursor" Value="Hand"/>
</Style>
</ListBox.ItemContainerStyle>
<ListBox.ItemTemplate>
</ItemsControl.ItemsPanel>
<!--<ListBox.ItemTemplate>
<DataTemplate>
<Border
BorderBrush="Black"
@@ -40,5 +36,5 @@
</Border.ToolTip>
</Border>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</ListBox.ItemTemplate>-->
</c:TabularColorPicker>
@@ -9,7 +9,7 @@ namespace mbts.ui.controls.color_picker {
/// <summary>
/// Interaction logic for ColorPickerSwatch.xaml
/// </summary>
partial class ColorPickerSwatch : ListBox {
partial class ColorPickerSwatch : TabularColorPicker {
public ColorPickerSwatch() =>
InitializeComponent();
@@ -0,0 +1,123 @@
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:u="clr-namespace:mbts.ui"
xmlns:c="clr-namespace:mbts.ui.controls"
xmlns:cp="clr-namespace:mbts.ui.controls.color_picker"
>
<ControlTemplate x:Key="TabularColorPickerTemplate" TargetType="c:TabularColorPicker">
<Border
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
HorizontalAlignment="{TemplateBinding HorizontalAlignment}"
VerticalAlignment="{TemplateBinding VerticalAlignment}"
Width="{TemplateBinding Width}"
Height="{TemplateBinding Height}"
Padding="{TemplateBinding Padding}"
>
<ItemsPresenter/>
</Border>
</ControlTemplate>
<Style x:Key="TabularColorPickerItemStyle" TargetType="ListBoxItem">
<Setter Property="Padding" Value="0"/>
<Setter Property="BorderThickness" Value="1"/>
<Setter Property="Width" Value="19"/>
</Style>
<DataTemplate x:Key="NamedColorTemplate" DataType="{x:Type cp:NamedColorVM}">
<Border
x:Name="Bd"
BorderThickness="1"
Width="18"
Height="18"
>
<Border.BorderBrush>
<SolidColorBrush Color="{Binding color}"/>
</Border.BorderBrush>
<Border.Background>
<SolidColorBrush Color="{Binding color}"/>
</Border.Background>
</Border>
<DataTemplate.Triggers>
<DataTrigger Binding="{Binding IsMouseOver, RelativeSource={RelativeSource AncestorType=ListBoxItem}}" Value="True">
<Setter TargetName="Bd" Property="BorderBrush" Value="Black"/>
</DataTrigger>
<DataTrigger Binding="{Binding IsSelected, RelativeSource={RelativeSource AncestorType=ListBoxItem}}" Value="True">
<Setter TargetName="Bd" Property="BorderBrush" Value="Black"/>
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>
<ControlTemplate x:Key="TabularColorPickerItemTemplate" TargetType="ListBoxItem">
<Border
x:Name="RootBorder"
BorderThickness="{TemplateBinding BorderThickness}"
Width="{TemplateBinding Width}"
Height="{TemplateBinding Height}"
HorizontalAlignment="{TemplateBinding HorizontalAlignment}"
VerticalAlignment="{TemplateBinding VerticalAlignment}"
Padding="{TemplateBinding Padding}"
>
<u:StateManager.Groups>
<u:StateGroupsManager AttachToMembers="True">
<u:StateDefinitionCollection>
<u:MouseStateDefinition MouseSource="{Binding Group.Target, RelativeSource={RelativeSource Self}}"/>
</u:StateDefinitionCollection>
</u:StateGroupsManager>
</u:StateManager.Groups>
<VisualStateManager.VisualStateGroups>
<VisualStateGroup Name="MouseStates">
<VisualState Name="Normal"/>
<VisualState Name="MouseOver">
<Storyboard>
<ColorAnimation Storyboard.TargetName="RootBorder" Storyboard.TargetProperty="BorderBrush.Color" BeginTime="0" Duration="0" To="#80FFFFFF"/>
<DoubleAnimation Storyboard.TargetName="RootBorder" Storyboard.TargetProperty="BorderBrush.Opacity" BeginTime="0" Duration="0" To="1"/>
</Storyboard>
</VisualState>
<VisualState Name="Pressed">
<Storyboard>
<DoubleAnimation Storyboard.TargetName="RootBorder" Storyboard.TargetProperty="Background.Opacity" BeginTime="0" Duration="0" To="0.5"/>
<ColorAnimation Storyboard.TargetName="RootBorder" Storyboard.TargetProperty="BorderBrush.Color" BeginTime="0" Duration="0" To="#80FFFFFF"/>
<DoubleAnimation Storyboard.TargetName="RootBorder" Storyboard.TargetProperty="BorderBrush.Opacity" BeginTime="0" Duration="0" To="1"/>
</Storyboard>
</VisualState>
</VisualStateGroup>
<VisualStateGroup Name="SelectionStates">
<VisualState Name="Unselected"/>
<VisualState Name="Selected">
<Storyboard>
<DoubleAnimation Storyboard.TargetName="RootBorder" Storyboard.TargetProperty="BorderBrush.Opacity" BeginTime="0" Duration="0" To="1"/>
</Storyboard>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
<Border.Background>
<SolidColorBrush Color="{Binding color}" Opacity="1"/>
</Border.Background>
<Border.BorderBrush>
<SolidColorBrush Color="Black" Opacity="0"/>
</Border.BorderBrush>
<Grid>
<ContentPresenter
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
/>
</Grid>
</Border>
</ControlTemplate>
<Style TargetType="c:TabularColorPicker">
<Setter Property="Template" Value="{StaticResource TabularColorPickerTemplate}"/>
<Setter Property="ItemsSource" Value="{x:Static cp:ColorUtils.WebColorVMs}"/>
<Setter Property="ItemTemplate" Value="{StaticResource NamedColorTemplate}"/>
<Setter Property="ItemsPanel">
<Setter.Value>
<ItemsPanelTemplate>
<UniformGrid Rows="7"/>
</ItemsPanelTemplate>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>
@@ -0,0 +1,25 @@
using mbts.ui.converters;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
namespace mbts.ui.controls {
/// <summary>
/// Interaction logic for ColorPickerPopup.xaml
/// </summary>
class TabularColorPicker : ListBox {
public static readonly IValueConverter AlphaToOpacityConverter =
ValueConverter.create<byte, double>(alpha => alpha / 255.0);
static TabularColorPicker() =>
DefaultStyleKeyProperty.OverrideMetadata(typeof(TabularColorPicker), new FrameworkPropertyMetadata(typeof(TabularColorPicker)));
public static readonly DependencyProperty SelectedOpacityProperty =
WPFHelper.create_dp<byte, TabularColorPicker>(nameof(SelectedOpacity), 255);
public byte SelectedOpacity {
get => (byte)GetValue(SelectedOpacityProperty);
set => SetValue(SelectedOpacityProperty, value);
}
}
}
@@ -1,4 +1,5 @@
using mbts.ui.converters;
using mbts.ui.dpi;
using mbts.ui.converters;
using mbts.ui.shell;
using mbts.ui.utility.text;
using System;
@@ -10,6 +11,7 @@ using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Media;
using bts;
namespace mbts.ui.controls {
partial class DIYGrid {
@@ -84,12 +86,11 @@ namespace mbts.ui.controls {
}
public static Rect? get_clip(Size content_size, double row_height, double left, double unclipped_left, double width) =>
content_size == null ? get_clip(0, 0, row_height, left, unclipped_left, width) :
get_clip(content_size.Width, content_size.Height, row_height, left, unclipped_left, width);
get_clip(content_size.Width, content_size.Height, row_height, left, unclipped_left, width);
public static Rect? get_clip(double content_width, double content_height, double row_height, double left, double unclipped_left, double width) {
if (content_width > width || unclipped_left < left)
return new Rect(left, 0, width - left + unclipped_left, row_height);
return new Rect(left, 0, width - left + unclipped_left, row_height + 1);
return null;
}
@@ -97,7 +98,10 @@ namespace mbts.ui.controls {
draw_with_clip(dc,
raw_content_width + padding.Left + padding.Right,
raw_content_height + padding.Top + padding.Bottom,
row_height, position.left, position.unclipped_left, position.width);
row_height,
position.left.RoundLayoutValue(),
position.unclipped_left.RoundLayoutValue(),
position.width.RoundLayoutValue());
public static IDisposable draw_with_clip(DrawingContext dc, double content_width, double content_height, double row_height, double left, double unclipped_left, double width) {
var clip = get_clip(content_width, content_height, row_height, left, unclipped_left, width);
@@ -108,54 +112,38 @@ namespace mbts.ui.controls {
}
public static void draw_text(
DrawingContext dc, GlyphContext glyph_context, double font_size, string text, ColumnGeometry position, double row_height,
Brush foreground, Brush background, HorizontalAlignment x_align, VerticalAlignment y_align, Thickness padding) {
DrawingContext dc,
GlyphContext glyph_context,
double font_size,
string text,
ColumnGeometry position,
double row_height,
Brush foreground,
Brush background,
HorizontalAlignment x_align,
VerticalAlignment y_align,
Thickness padding,
bool round_to_layout = true) {
//
if (!string.IsNullOrEmpty(text)) {
var gr = glyph_context.get_glyph_run(text, font_size, position.unclipped_left, 0, position.right, row_height, x_align, y_align, padding);
draw_text(dc, gr, gr.AdvanceWidths.Sum(), font_size, position, padding, row_height, foreground, background);
}
var gr = glyph_context.get_glyph_run(text, font_size, position.unclipped_left, 0, position.right, row_height, x_align, y_align, padding, round_to_layout);
var gwidth = gr == null ? 0 : gr.ComputeAlignmentBox().Width;
draw_text(dc, gr, gwidth, font_size, position, padding, row_height, foreground, background);
}
public static void draw_text(DrawingContext dc, GlyphRun glyph_run, double glyph_width, double font_size, ColumnGeometry position, Thickness padding, double row_height, Brush foreground, Brush background) {
using (draw_with_clip(dc, glyph_width, font_size, padding, row_height, position)) {
if (background != null)
dc.DrawRectangle(background, null, new Rect(position.left, 0, position.width, row_height));
dc.DrawGlyphRun(foreground, glyph_run);
if (glyph_run == null && background == null)
return;
var rect = position.to_rect(row_height);
if (background != null) {
var guidelines = rect.AntiAliasGuidelines(0);
dc.PushGuidelineSet(guidelines);
dc.DrawRectangle(background, null, rect);
dc.Pop();
}
}
public static void draw_text(
DrawingContext dc, GlyphContext glyph_context, double font_size, string text, ColumnGeometry position, double row_height,
Brush foreground, Brush background, HorizontalAlignment x_align, VerticalAlignment y_align, Thickness padding, IEnumerable<CellAnimation> trigger_actions) {
//
using (position.clip_draw(dc, row_height)) {
double adjusted_left = position.unclipped_left;
double available_width = position.width;
if (background != null)
dc.DrawRectangle(background, null, new Rect(position.left, 0, position.width, row_height));
// store any trigger-glyphs in a list, to be drawn after iterating through all triggers (must draw background first, so glyphs will not be covered up).
List<(Brush brush, GlyphRun glyph)> glyphs = new List<(Brush brush, GlyphRun glyph)>(1);
if (trigger_actions != null) {
foreach (CellAnimation ta in trigger_actions) {
switch (ta.animation_type_case) {
case CellAnimation.AnimationTypeOneofCase.BackgroundAnimation:
dc.DrawRectangle(ta.background_animation.brush, ta.background_animation.pen, new Rect(position.left, 0, position.width, row_height));
break;
case CellAnimation.AnimationTypeOneofCase.SymbolAnimation:
var (symbol_text, is_sideways) = _get_symbol_animation_args(ta.symbol_animation);
GlyphRun symbol_glyph = glyph_context.get_glyph_run(symbol_text, font_size, position.width, row_height, HorizontalAlignment.Left, adjusted_left, y_align, 0, is_sideways: is_sideways);
glyphs.Add((ta.symbol_animation.brush, symbol_glyph));
adjusted_left += symbol_glyph.AdvanceWidths.Sum();
break;
}
}
}
foreach (var (brush, glyph) in glyphs) {
dc.DrawGlyphRun(brush, glyph);
}
GlyphRun main_content = glyph_context.get_glyph_run(text, font_size, adjusted_left, 0, position.right, row_height, x_align, y_align, padding);
draw_text(dc, main_content, main_content.AdvanceWidths.Sum(), font_size, position, padding, row_height, foreground, background);
if (glyph_run != null) {
dc.PushClip(new RectangleGeometry(rect));
dc.DrawGlyphRun(foreground, glyph_run);
dc.Pop();
}
}
@@ -13,6 +13,12 @@ using System.Windows.Controls;
using System.Windows.Media;
namespace mbts.ui.controls {
class DIYGridColumn : DIYGrid.ColumnBase<string> {
protected override void on_key_initialized(string key) {
if (id == null && key != null)
id = key.ToString();
}
}
// Column types are nested inside the DIYGrid
partial class DIYGrid {
public static class DefaultColumnWidths {
@@ -1,30 +1,39 @@
using System;
using System.Windows;
using System.Windows.Media;
namespace mbts.ui.controls {
// Column types are nested inside the DIYGrid
partial class DIYGrid {
public struct ColumnGeometry {
public ColumnBase column;
public DIYGridSuperHeader super_column;
public double unclipped_left; /* If the column was unclipped */
public double left;
public double width { get { return column.ActualWidth; } }
public double right { get { return unclipped_left + width; } }
public readonly struct ColumnGeometry {
public readonly ColumnBase column;
public readonly DIYGridSuperHeader super_column;
public readonly double
left,
// If the column was unclipped
unclipped_left,
right;
public ColumnGeometry(ColumnBase column, DIYGridSuperHeader super_column, double left, double right)
: this(column, super_column, left, left, right) { }
public ColumnGeometry(ColumnBase column, DIYGridSuperHeader super_column, double left, double unclipped_left, double right) {
this.column = column;
this.super_column = super_column;
this.left = left;
this.unclipped_left = left;
this.right = right;
}
public double width => right - left;
public bool is_partially_obscured { get { return unclipped_left < left; } }
public double visible_width => Math.Max(0, width - left + unclipped_left);
public override string ToString() {
return "{3} {0:0.0},{1:0.0} w{2:0.0}".fmt(left, right, width, column.Header);
}
public string group { get { return super_column == null ? "" : super_column.FullHeader; } }
public RectangleGeometry get_clip(double row_height) =>
new RectangleGeometry(new Rect(left, 0, width - left + unclipped_left, row_height));
public Rect to_rect(double row_height) =>
new Rect(left, 0, width, row_height);
public IDisposable clip_draw(DrawingContext dc, double row_height) {
dc.PushClip(get_clip(row_height));
return Disposable.Create(dc.Pop);
public override string ToString() {
return "{3} {0:0.0},{1:0.0} w{2:0.0}".fmt(left, right, width, column.Header);
}
}
}
@@ -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 });
@@ -18,12 +18,15 @@ namespace mbts.ui.controls {
public IDataGridColumnFilter filter { get; }
public virtual string filter_template => DataGridFilterKnownTemplates.AlgoType;
public BindingBase font_weight_binding { get; set; }
public Thickness padding { get; set; } = DefaultCellPadding;
public override FrameworkElement view_factory() {
var v = new OrderTypeItem {
FontFamily = font_family,
VerticalAlignment = VerticalAlignment.Center,
VerticalContentAlignment = VerticalAlignment.Center
HorizontalAlignment = HorizontalAlignment.Stretch,
VerticalAlignment = VerticalAlignment.Stretch,
VerticalContentAlignment = VerticalAlignment.Center,
Padding = padding
};
v.SetBinding(OrderTypeItem.BadgeFillProperty, _badge_fill_binding);
v.SetBinding(OrderTypeItem.DisplayNameProperty, _display_name_binding);
@@ -32,10 +35,11 @@ namespace mbts.ui.controls {
} else {
bind(FontWeightProperty, nameof(font_weight));
}
bind(BackgroundProperty, nameof(background_brush));
bind(FontFamilyProperty, nameof(font_family));
bind(FontSizeProperty, nameof(font_size));
bind(ForegroundProperty, nameof(font_brush));
bind(HorizontalAlignmentProperty, nameof(horizontal_alignment));
bind(HorizontalContentAlignmentProperty, nameof(horizontal_alignment));
bind(OrderTypeItem.WidthProperty, nameof(ActualWidth));
return v;
//
@@ -11,6 +11,7 @@ namespace mbts.ui.controls {
this.filter = filter;
this.filter_template = filter_template;
_binding = binding;
background_binding = new Binding(nameof(background_brush)) { Source = this };
}
public IDataGridColumnFilter filter { get; }
@@ -2,6 +2,7 @@
using bts.utils;
using mbts.spark.filters;
using mbts.ui.controls.adorners;
using mbts.ui.dpi;
using mbts.ui.extensions;
using mbts.ui.settings;
using mbts.ui.utility;
@@ -442,8 +443,12 @@ namespace mbts.ui.controls {
static DIYGrid() {
DefaultStyleKeyProperty.OverrideMetadata(typeof(DIYGrid), new FrameworkPropertyMetadata(typeof(DIYGrid)));
UseLayoutRoundingProperty.OverrideMetadata(typeof(DIYGrid), new FrameworkPropertyMetadata(OnUseLayoutRoundingChanged));
DefaultSelectionHighlight.Freeze();
DefaultSelectionStroke.Freeze();
void OnUseLayoutRoundingChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) =>
d.CoerceValue(RowHeightProperty);
}
public DIYGrid() {
@@ -1069,7 +1074,7 @@ namespace mbts.ui.controls {
if (v < min_row_height)
return min_row_height;
}
return v;
return o.UseLayoutRounding ? v.RoundLayoutValue() : v;
}, DefaultRowHeight);
public event Action<double> RowHeightChanged;
@@ -1146,33 +1151,30 @@ namespace mbts.ui.controls {
double frozen_offset = 0;
int vis_frozen_col_count = _get_visible_frozen_column_count();
foreach (var col in _visible_columns.Take(vis_frozen_col_count)) {
yield return new ColumnGeometry() {
column = col,
left = frozen_offset,
unclipped_left = frozen_offset,
};
double right = frozen_offset + col.ActualWidth;
right = right.RoundLayoutValue();
yield return new ColumnGeometry(col, null, frozen_offset, frozen_offset, right);
frozen_offset += col.ActualWidth;
}
// frozen_offset will be off by 2 pixels because of the GridViewHeaderRowPresenter PaddingHeader (see c_PaddingHeaderMinWidth in referencesource)
// We could use the _header_frozen.ActualWidth but due to the async nature of layout it get updated slightly too late
frozen_offset += 2;
if (_frozen_column_count > 0) {
// frozen_offset will be off by 2 pixels because of the GridViewHeaderRowPresenter PaddingHeader (see c_PaddingHeaderMinWidth in referencesource)
// We could use the _header_frozen.ActualWidth but due to the async nature of layout it get updated slightly too late
frozen_offset += 2;
}
// Adjust by scroll amount
double offset = frozen_offset - (_scroll_horizontal.Value);
double offset = frozen_offset - _scroll_horizontal.Value;
offset = offset.RoundLayoutValue();
int count = 0;
foreach (var col in _visible_columns.Skip(vis_frozen_col_count)) {
double left = Math.Max(offset, frozen_offset);
double right = offset + col.ActualWidth;
right = right.RoundLayoutValue();
// If right < frozen_offset column is completely obscured
if (include_obscured_cols || (right > frozen_offset && left <= ActualWidth)) {
// fully visible
yield return new ColumnGeometry() {
column = col,
super_column = _superheader_column_index.ElementAtOrDefault(count),
unclipped_left = offset,
left = left
};
yield return new ColumnGeometry(col, _superheader_column_index.ElementAtOrDefault(count), left, offset, right);
}
offset += col.ActualWidth;
offset = right;
++count;
}
}
@@ -55,6 +55,7 @@
<Setter Property="BorderThickness" Value="0"/>
<Setter Property="Focusable" Value="False"/>
<Setter Property="SelectionMode" Value="Extended"/>
<Setter Property="UseLayoutRounding" Value="True"/>
<Setter Property="ColumnHeaderStyle">
<Setter.Value>
<Style TargetType="GridViewColumnHeader">
@@ -1,4 +1,5 @@
using bts.utils;
using mbts.ui.dpi;
using mbts.ui.extensions;
using System.Collections;
using System.Collections.Generic;
@@ -123,7 +124,7 @@ namespace mbts.ui.controls {
protected override Size ArrangeOverride(Size finalSize) {
foreach (var kv in _column_layout_map) {
if (_element_cells.TryGetValue(kv.Key, out FrameworkElement ec))
ec.Arrange(new Rect(new Point(kv.Value.left, 0), new Size(kv.Value.width, _row_height)));
ec.Arrange(kv.Value.to_rect(_row_height));
}
return finalSize;
}
@@ -53,11 +53,7 @@ namespace mbts.ui.controls {
if (col.is_partially_obscured) {
var obscured_width = col.left - col.unclipped_left;
grid.scroll_horizontal(-obscured_width);
return new ColumnGeometry() {
column = col.column,
left = col.left,
unclipped_left = col.left
};
return new ColumnGeometry(col.column, col.super_column, col.left, col.left, col.right);
}
return col;
}
@@ -1,18 +1,11 @@
using bts.collections;
using bts.proto;
using mbts.collections;
using bts.utils;
using mbts.ui;
using mbts.ui.controls;
using mbts.ui.settings;
using mbts.ui.shell;
using mbts.ui.utility;
using mbts.ui.viewmodels;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reactive.Linq;
using System.Windows;
using System.Windows.Media;
using CellAnimation = mbts.ui.CellAnimation;
namespace mbts.spark {
/// <summary>
@@ -28,172 +21,110 @@ namespace mbts.spark {
ImmutableFontColor = 1,
ImmutableFontWeight = 1 << 1,
ImmutableHeader = 1 << 2,
ImmutableBackgroundColor = 1 << 3
ImmutableBackgroundColor = 1 << 3,
ImmutableVisibility = 1 << 4
}
partial class GridSettingsVM<TRowData, TPredicateData, TSettingsData> {
public interface IColumnVM : ISimpleColumnVM, IValueFactory<(GridColumnSettings settings, ILookup<TPredicateData, CellAnimation> animations)>, IDisposable {
ObservableEditableCollection<PredicateCellAnimationVM> predicate_animations { get; }
new (GridColumnSettings settings, ILookup<TPredicateData, CellAnimation> animations) create();
new void load_from((GridColumnSettings settings, ILookup<TPredicateData, CellAnimation> animations) args);
class GridColumnSettingsVM : ParameterGroupVM {
string _id = string.Empty;
GridColumnSettings _settings;
public GridColumnSettingsVM()
: this(GridColumnSettingsFeatureFlags.None) { }
protected GridColumnSettingsVM(GridColumnSettingsFeatureFlags? init_flags) : base("") {
if (font_color.override_value.A == 0)
font_color.set_override(font_color.override_value.with(255));
if (init_flags.HasValue)
apply_flags(init_flags.Value);
}
public class ColumnVM : DisposableViewModelBase, IColumnVM {
public readonly GridSettingsVM<TRowData, TPredicateData, TSettingsData> owner;
public string id { get => _id; private set => Set(ref _id, value); }
public StringOverrideParameterVM header { get; } = new StringOverrideParameterVM("Header");
public BooleanOverrideParameterVM show { get; } = new BooleanOverrideParameterVM("Show");
public DoubleParameterVM width { get; } = new DoubleParameterVM("Width");
public ColorOverrideParameterVM background { get; } = new ColorOverrideParameterVM("Background");
public ColorOverrideParameterVM font_color { get; } = new ColorOverrideParameterVM("Font Color");
public FontWeightOverrideParameterVM font_weight { get; } = new FontWeightOverrideParameterVM();
public HorizontalAlignmentParameterVM horizontal_alignment { get; } = new HorizontalAlignmentParameterVM("Horizontal Alignment");
string
_id = string.Empty,
_display_name = string.Empty;
double _width = 70;
HorizontalAlignment _horizontal_alignment = HorizontalAlignment.Left;
bool
_is_visible,
_can_override_display_name = true,
_can_override_font_color = true,
_is_font_color_overridden,
_can_override_font_weight = true,
_is_font_weight_overridden,
_can_override_background_color = true,
_is_background_color_overridden;
FontWeight _font_weight_override;
GridColumnSettings _settings;
public ColumnVM(GridSettingsVM<TRowData, TPredicateData, TSettingsData> owner) {
this.owner = owner;
// animations
predicate_animations = new ObservableEditableCollection<PredicateCellAnimationVM>(() =>
new PredicateCellAnimationVM(id, owner._create_column_predicate_vm(id), owner._predicate_data_factory), true);
predicate_animations.CollectionChanged += WPFHelper.create_collection_changed_cb(on_items_added: (_, __) => owner.show_cell_predicate_animations = true);
_dispose = Disposable.Create(() => predicate_animations.OfType<IDisposable>().DisposeAll());
}
public string id { get => _id; private set => Set(ref _id, value); }
public double width { get => _width; set => Set(ref _width, value); }
public HorizontalAlignment horizontal_alignment { get => _horizontal_alignment; set => Set(ref _horizontal_alignment, value); }
public bool is_visible { get => _is_visible; set => Set(ref _is_visible, value); }
// display name
public bool can_override_display_name { get => _can_override_display_name; private set => Set(ref _can_override_display_name, value); }
public string display_name { get => _display_name; set => Set(ref _display_name, value); }
// font color override
public bool can_override_font_color { get => _can_override_font_color; private set => Set(ref _can_override_font_color, value); }
public bool is_font_color_overridden { get => _is_font_color_overridden; set => Set(ref _is_font_color_overridden, value); }
public ColorPickerPopupVM font_color_override { get; } = new ColorPickerPopupVM();
// font weight override
public bool can_override_font_weight { get => _can_override_font_weight; private set => Set(ref _can_override_font_weight, value); }
public bool is_font_weight_overridden { get => _is_font_weight_overridden; set => Set(ref _is_font_weight_overridden, value); }
public FontWeight font_weight_override { get => _font_weight_override; set => Set(ref _font_weight_override, value); }
// background color override
public bool can_override_background_color { get => _can_override_background_color; private set => Set(ref _can_override_background_color, value); }
public bool is_background_color_overridden { get => _is_background_color_overridden; set => Set(ref _is_background_color_overridden, value); }
public ColorPickerPopupVM background_color_override { get; } = new ColorPickerPopupVM() { bind_opacity_to_alpha = true };
// animations
public ObservableEditableCollection<PredicateCellAnimationVM> predicate_animations { get; }
public (GridColumnSettings settings, ILookup<TPredicateData, CellAnimation> animations) create() {
var settings = _settings.with(
can_override_display_name ? display_name : null,
is_visible,
null,
is_font_weight_overridden ? new NullableFontWeightProto(font_weight_override) : new NullableFontWeightProto(null),
is_font_color_overridden ? new NullableColorProto(font_color_override.color) : new NullableColorProto(null),
width,
horizontal_alignment,
background_color_override: is_background_color_overridden ?
new NullableColorProto(background_color_override.color) : default);
var animations_lookup = predicate_animations.ToFlatLookup(pa => pa.predicate.create_assigned(owner._predicate_data_factory), pa => pa.animations.Select(a => a.create()));
return (settings, animations_lookup);
}
public void load_from((GridColumnSettings settings, ILookup<TPredicateData, CellAnimation> animations) value) {
_settings = value.settings;
id = _settings.id;
display_name = _settings.display_name;
width = _settings.width;
horizontal_alignment = _settings.horizontal_alignment;
is_visible = _settings.is_visible;
// font color
is_font_color_overridden = _settings.font_color_override.HasValue;
if (is_font_color_overridden) {
font_color_override.load_from(new SolidColorBrush(_settings.font_color_override.Value));
}
// font weight
is_font_weight_overridden = _settings.font_weight_override.HasValue;
if (is_font_weight_overridden) {
_font_weight_override = _settings.font_weight_override.Value;
}
// background color
is_background_color_overridden = _settings.background_color_override.HasValue;
if (is_background_color_overridden) {
background_color_override.color = _settings.background_color_override.Value;
}
predicate_animations.SetItems(value.animations.empty_if_null().Select(g => {
var vm = new PredicateCellAnimationVM(id, owner._create_column_predicate_vm(id), owner._predicate_data_factory);
vm.load_from((g.Key, g));
return vm;
}));
}
public void apply_flags(GridColumnSettingsFeatureFlags flags) {
// display name
can_override_display_name = (flags & GridColumnSettingsFeatureFlags.ImmutableHeader) == 0;
if (!can_override_display_name)
display_name = id;
// font color
can_override_font_color = (flags & GridColumnSettingsFeatureFlags.ImmutableFontColor) == 0;
is_font_color_overridden &= can_override_font_color;
// font weight
can_override_font_weight = (flags & GridColumnSettingsFeatureFlags.ImmutableFontWeight) == 0;
is_font_weight_overridden &= can_override_font_weight;
// background color
can_override_background_color = (flags & GridColumnSettingsFeatureFlags.ImmutableBackgroundColor) == 0;
is_background_color_overridden &= can_override_background_color;
}
public override string ToString() => $"{display_name} ({id})";
GridColumnSettings IValueFactory<GridColumnSettings>.create() =>
create().settings;
void IValueFactory<GridColumnSettings>.load_from(GridColumnSettings value) =>
load_from((value, new Dictionary<TPredicateData, CellAnimation>().ToLookup(kv => kv.Key, kv => kv.Value)));
public GridColumnSettings create() {
return _settings.with(
header.effective_is_overridden ? header.override_value : id,
show.effective_value,
null,
_get_optional_font_weight_proto(font_weight),
_get_optional_color_proto(font_color),
width.value,
horizontal_alignment.value,
null,
_get_optional_color_proto(background));
}
public class PredicateCellAnimationVM : DisposableViewModelBase, IValueFactory<(TPredicateData predicate, IEnumerable<CellAnimation> animations)> {
//
readonly string _col_id;
readonly Property<bool> _show_animations;
readonly Func<TPredicateData> _predicate_data_factory;
public PredicateCellAnimationVM(string col_id, IPropertyWrapperVM<TPredicateData> predicate, Func<TPredicateData> predicate_data_factory) {
_col_id = col_id;
this.predicate = predicate;
_predicate_data_factory = predicate_data_factory;
_show_animations = backing(nameof(show_animations), true);
animations = new ObservableEditableCollection<CellAnimationVM>(() => {
var vm = new CellAnimationVM(col_id);
vm.selected_animation_type = vm.animation_types.FirstOrDefault();
return vm;
}, true);
animations.CollectionChanged += WPFHelper.create_collection_changed_cb(on_items_added: (_, __) => _show_animations.set(true));
_dispose = predicate as IDisposable;
}
public IPropertyWrapperVM<TPredicateData> predicate { get; }
public ObservableEditableCollection<CellAnimationVM> animations { get; }
public IEnumerable<CellAnimation.AnimationTypeOneofCase> animation_types { get; } = EnumHelper.get_values_except(CellAnimation.AnimationTypeOneofCase.None);
public bool show_animations { get => _show_animations.Value; set => _show_animations.set(value); }
public (TPredicateData predicate, IEnumerable<CellAnimation> animations) create() =>
(predicate.create_assigned(_predicate_data_factory), animations.Select(a => a.create()));
public void load_from((TPredicateData predicate, IEnumerable<CellAnimation> animations) args) {
predicate.load_from(args.predicate);
animations.SetItems(args.animations.Select(a => {
var vm = new CellAnimationVM(_col_id);
vm.load_from(a);
return vm;
}));
public void load_from(GridColumnSettings value) {
_settings = value;
id = _settings.id;
header.default_value = _settings.id;
header.override_value = _settings.display_name;
width.value = _settings.width;
horizontal_alignment.value = _settings.horizontal_alignment;
show.override_value = value.is_visible;
// font color
font_color.is_overridden = _settings.font_color_override.HasValue;
if (font_color.can_override) {
if (font_color.is_overridden)
font_color.override_value = _settings.font_color_override.Value;
}
// font weight
font_weight.is_overridden = _settings.font_weight_override.HasValue;
if (font_weight.is_overridden)
font_weight.override_value = _settings.font_weight_override.Value;
// background
background.is_overridden = _settings.background_color_override.HasValue;
if (background.is_overridden)
background.override_value = _settings.background_color_override.Value;
}
public void apply_flags(GridColumnSettingsFeatureFlags flags) {
header.can_override = (flags & GridColumnSettingsFeatureFlags.ImmutableHeader) == 0;
show.can_override = (flags & GridColumnSettingsFeatureFlags.ImmutableVisibility) == 0;
font_weight.can_override = (flags & GridColumnSettingsFeatureFlags.ImmutableFontWeight) == 0;
font_color.can_override = (flags & GridColumnSettingsFeatureFlags.ImmutableFontColor) == 0;
background.can_override = (flags & GridColumnSettingsFeatureFlags.ImmutableBackgroundColor) == 0;
sync_params();
}
public virtual void sync_params() {
var layout = new HeaderedParameterGroupVM("Column Layout");
var fonts_and_colors = new HeaderedParameterGroupVM("Fonts/Colors");
if (header.can_override)
layout.parameters.Add(header);
layout.parameters.Add(width);
layout.parameters.Add(horizontal_alignment);
if (font_weight.can_override)
fonts_and_colors.parameters.Add(font_weight);
if (font_color.can_override)
fonts_and_colors.parameters.Add(font_color);
if (background.can_override)
fonts_and_colors.parameters.Add(background);
parameters.SetItems(layout, fonts_and_colors);
}
public override string ToString() =>
header.ToString();
static NullableColorProto? _get_optional_color_proto(ColorOverrideParameterVM optional_color) =>
optional_color.can_override ?
optional_color.is_overridden ?
new NullableColorProto(optional_color.override_value) :
new NullableColorProto() :
(NullableColorProto?)null;
static NullableFontWeightProto? _get_optional_font_weight_proto(ParameterOverrideVM<FontWeight> optional_font_weight) =>
optional_font_weight.can_override ?
optional_font_weight.is_overridden ?
new NullableFontWeightProto(optional_font_weight.override_value) :
new NullableFontWeightProto() :
(NullableFontWeightProto?)null;
}
}
@@ -16,7 +16,7 @@ namespace mbts.ui.settings {
const string _default_font_family = "Segoe UI";
const double _default_font_size = 11;
const uint
_default_font_color = 0,
_default_font_color = 0xFF000000,
_default_background = 0x00FFFFFF;
const ushort _default_font_weight = 400;
const uint _default_background_color = 0x00FFFFFF; // Colors.Transparent
@@ -1,12 +1,8 @@
using bts;
using bts.api;
using bts.collections;
using bts.shell;
using bts.utils;
using mbts.spark;
using mbts.ui.settings;
using mbts.ui.shell;
using mbts.utils;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
@@ -16,73 +12,31 @@ using System.Windows.Media;
using static mbts.ui.controls.DIYGrid;
namespace mbts.ui {
class GridSettingsManager<TRow, TRowData, TPredicateData, TSettingsData> :
ViewModelBase, IValueFactory<TSettingsData>, IObserver<TSettingsData>, IDisposable
where TRow : IRowVM
where TSettingsData : class, IGridSettingsWithNotifications<TRowData, TPredicateData> {
class GridSettingsManager : ViewModelBase {
//
readonly IBus _bus;
readonly OrdersModel _orders_model;
readonly List<IDisposable> _notification_subs = new List<IDisposable>();
readonly ObservableCollection<ColumnBase> _columns;
readonly IEnumerable<TRow> _rows;
readonly Property<GridSettings> _grid_settings;
readonly DelegateProperty<FontFamily> _font_family;
readonly DelegateProperty<FontWeight> _font_weight;
readonly DelegateProperty<double> _font_size;
readonly DelegateProperty<SolidColorBrush> _font_color, _selection_highlight, _background_color;
readonly SetMap<IPredicate<TRowData>, AnimationDefinitionWrapper<TRow>> _animation_defs = new SetMap<IPredicate<TRowData>, AnimationDefinitionWrapper<TRow>>();
readonly Func<TPredicateData> _predicate_data_factory;
readonly Func<TPredicateData, IPredicate<TRowData>> _predicate_factory;
readonly Func<TPredicateData, INotificationPredicate<TRowData>> _notification_predicate_factory;
readonly Func<IPropertyWrapperVM<TPredicateData>> _create_predicate_vm;
readonly Func<string, IPropertyWrapperVM<TPredicateData>> _create_column_predicate_vm;
readonly Func<GridSettings, IEnumerable<IPredicateAnimationsData<TRowData, TPredicateData>>, TSettingsData> _create_settings_obj;
readonly Func<IPredicate<TRowData>, SoundNotification, IDisposable> _link_sound;
readonly Func<INotificationPredicate<TRowData>, IDisposable> _link_toast_notification;
readonly IReadOnlyDictionary<string, ImmutableColumnSettingStore> _column_flags;
IEnumerable<IPredicateAnimationsData<TRowData, TPredicateData>> _predicate_animations_data;
public GridSettingsManager(
IBus bus,
OrdersModel orders_model,
IEnumerable<TRow> rows,
ObservableCollection<ColumnBase> columns,
Func<TPredicateData> predicate_data_factory,
Func<IPropertyWrapperVM<TPredicateData>> create_predicate_vm,
Func<string, IPropertyWrapperVM<TPredicateData>> create_column_predicate_vm,
Func<GridSettings, IEnumerable<IPredicateAnimationsData<TRowData, TPredicateData>>, TSettingsData> create_settings_obj,
Func<TPredicateData, IPredicate<TRowData>> predicate_factory,
Func<TPredicateData, INotificationPredicate<TRowData>> notification_predicate_factory,
Func<IPredicate<TRowData>, SoundNotification, IDisposable> link_sound,
Func<INotificationPredicate<TRowData>, IDisposable> link_toast_notification,
IReadOnlyDictionary<string, GridColumnSettingsFeatureFlags> column_flags = null) {
//
_bus = bus;
_orders_model = orders_model;
_rows = rows;
_columns = columns;
_predicate_data_factory = predicate_data_factory;
_predicate_factory = predicate_factory;
_notification_predicate_factory = notification_predicate_factory;
_create_predicate_vm = create_predicate_vm;
_create_column_predicate_vm = create_column_predicate_vm;
_create_settings_obj = create_settings_obj;
_link_sound = link_sound;
_link_toast_notification = link_toast_notification;
_column_flags = column_flags.ToDictionary(kv => kv.Key, kv => ImmutableColumnSettingStore.create(kv.Value));
_column_flags = column_flags?.ToDictionary(kv => kv.Key, kv => ImmutableColumnSettingStore.create(kv.Value));
_grid_settings = backing(nameof(grid_settings), GridSettings.Default);
_font_family = backing(nameof(font_family), () => new FontFamily(grid_settings.font_family), nameof(grid_settings));
_font_weight = backing(nameof(font_weight), () => grid_settings.font_weight, nameof(grid_settings));
_font_size = backing(nameof(font_size), () => grid_settings.font_size, nameof(font_size));
_font_color = backing(nameof(font_color), () => grid_settings.font_color.rgb_solid_brush().frozen(), nameof(grid_settings));
_selection_highlight = backing(nameof(selection_highlight), () => grid_settings.selection_highlight.to_brush().frozen(), nameof(grid_settings));
_selection_highlight = backing(nameof(selection_highlight), () => grid_settings.selection_highlight.to_argb_brush().frozen(), nameof(grid_settings));
_background_color = backing(nameof(background_color), () => grid_settings.background_color.argb_color().to_solid_brush().frozen(), nameof(grid_settings));
}
public void Dispose() =>
Disposable.dispose(_notification_subs);
public GridSettings grid_settings => _grid_settings.Value;
public FontFamily font_family => _font_family.Value;
public FontWeight font_weight => _font_weight.Value;
@@ -91,52 +45,25 @@ namespace mbts.ui {
public SolidColorBrush selection_highlight => _selection_highlight.Value;
public SolidColorBrush background_color => _background_color.Value;
public GridSettingsVM<TRowData, TPredicateData, TSettingsData> create_vm(GridSettings settings) {
var vm = new GridSettingsVM<TRowData, TPredicateData, TSettingsData>(_bus, _orders_model, _predicate_data_factory, _notification_predicate_factory, _create_predicate_vm, _create_column_predicate_vm, _create_settings_obj, _column_flags.ToDictionary(kv => kv.Key, kv => kv.Value.flags));
vm.load(_create_settings_obj(settings, _predicate_animations_data));
return vm;
public GridSettings create() {
try {
_grid_settings.set(grid_settings.with(columns: _columns.Select(c => c.create())));
} catch (Exception ex) {
Log.Ui.warn($"Error refreshing grid settings (may be benign, if caused by accessing a ColumnBase property after window closed): {ex}.");
}
return grid_settings;
}
public TSettingsData create() {
_grid_settings.set(grid_settings.with(_columns.Select(c => {
var col_settings = c.create();
if (_column_flags.TryGetValue(c.id, out ImmutableColumnSettingStore store))
col_settings = store.apply(col_settings);
return col_settings;
})));
return _create_settings_obj(grid_settings, _predicate_animations_data);
}
public GridSettingsVM create_vm() =>
new GridSettingsVM(_column_flags?.ToDictionary(kv => kv.Key, kv => kv.Value.flags));
public void load_from(TSettingsData value) => _load_from_core(value, true);
void _load_from_core(TSettingsData value, bool reset_immutables) {
Disposable.dispose(_notification_subs);
var grid = value.general ?? GridSettings.Default;
public void load_from(GridSettings grid) {
_grid_settings.set(grid);
foreach (ColumnBase col in _columns)
col.set_fallback_font_params(new FontFamily(grid.font_family), grid.font_size, grid.font_color.rgb_color(), grid.font_weight, grid.background_color.argb_color());
_predicate_animations_data = value.predicate_animations;
_animation_defs.clear();
foreach (var pa in _predicate_animations_data) {
var predicate = _predicate_factory(pa.predicate_data);
if (predicate == null)
continue;
foreach (var s in pa.sound_notifications) {
_notification_subs.Add(_link_sound(predicate, s));
}
var notification_predicate = _notification_predicate_factory(pa.predicate_data);
if (notification_predicate != null)
_notification_subs.Add(_link_toast_notification(notification_predicate));
}
// ** in the unfortunate event that we botch backwards compatability, and the columns don't bear a 1:1 relationship with the column-settings
// don't add insult to injury by crashing **
if (grid_settings.columns != null && _columns != null) {
if (reset_immutables) {
foreach (var kv in _column_flags) {
if (grid.columns.TryGetFirst(c => c.id == kv.Key, out GridColumnSettings cs))
kv.Value.store(cs);
}
}
var col_settings_id_map = grid_settings.columns.ToDictionary(cs => id_key(cs.id));
var columns_id_map = _columns.ToDictionary(c => id_key(c.id));
if (col_settings_id_map.Count == columns_id_map.Count && col_settings_id_map.Count == grid_settings.columns.Count) {
@@ -150,31 +77,8 @@ namespace mbts.ui {
col.load_from(cs);
}
// invalidate all the rows to draw them again to reflect the latest settings
foreach (var r in _rows) r.raise_invalidated();
//
string id_key(string id) => id.ToLower();
}
}
void IObserver<TSettingsData>.OnNext(TSettingsData sd) {
if (sd == null) return;
try {
var grid = sd.general;
if (grid.columns != null) {
var cols_with_applied_flags = grid.columns.ToList();
for (int i = 0; i < cols_with_applied_flags.Count; i++) {
if (_column_flags.TryGetValue(cols_with_applied_flags[i].id, out ImmutableColumnSettingStore store)) {
cols_with_applied_flags[i] = store.apply(cols_with_applied_flags[i]);
}
}
sd = _create_settings_obj(grid.with(cols_with_applied_flags), sd.predicate_animations);
}
_load_from_core(sd, false);
} catch (Exception ex) {
Log.Ui.error($"Error loading GridSettings (as observer): {ex}");
}
}
void IObserver<TSettingsData>.OnError(Exception error) { }
void IObserver<TSettingsData>.OnCompleted() { }
}
}
File diff suppressed because it is too large. Load diff
@@ -0,0 +1,17 @@
using mbts.ui.converters;
using System.Windows;
using System.Windows.Data;
namespace mbts.ui.spark {
/// <summary>
/// Interaction logic for GridSettingsView.xaml
/// </summary>
partial class GridSettingsResources : ResourceDictionary {
public GridSettingsResources() =>
InitializeComponent();
public static IMultiValueConverter ColumnDisplayNameVisibilityConverter =>
ValueConverter.create<bool, string, string, Visibility>((show_display_name, id, header) =>
!show_display_name || id == header ? Visibility.Collapsed : Visibility.Visible);
}
}
@@ -1,23 +1,20 @@
using bts.api;
using bts.collections;
using bts.collections;
using bts.proto;
using bts.shell;
using bts.utils;
using mbts.collections;
using mbts.ui;
using mbts.ui.controls;
using mbts.ui.settings;
using mbts.ui.shell;
using mbts.ui.utility;
using mbts.ui.utility.text;
using mbts.ui.viewmodels;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Reactive.Linq;
using System.Windows;
using CellAnimation = mbts.ui.CellAnimation;
using FontFamily = System.Windows.Media.FontFamily;
using System.Windows.Media;
namespace mbts.spark {
interface IGridSettingsWithNotifications<TRowData, TPredicateData> {
@@ -45,70 +42,65 @@ namespace mbts.spark {
EqualityComparer.safe_aggregate_hash(predicate_animations_data));
}
partial class GridSettingsVM<TRowData, TPredicateData, TSettingsData> : DisposableViewModelBase, IRequestViewClose, IWindowSettingsVM<TSettingsData>, ITemplateSelectorKey
where TSettingsData :class, IGridSettingsWithNotifications<TRowData, TPredicateData> {
//
protected readonly Func<IPropertyWrapperVM<TPredicateData>> _create_predicate_vm;
protected readonly Func<string, IPropertyWrapperVM<TPredicateData>> _create_column_predicate_vm;
readonly Func<TPredicateData> _predicate_data_factory;
readonly Func<GridSettings, IEnumerable<IPredicateAnimationsData<TRowData, TPredicateData>>, TSettingsData> _create_settings_obj;
readonly Property<IColumnVM> _current_column;
readonly Property<FontFamily> _font_family;
readonly Property<FontWeight> _font_weight;
readonly Property<string> _font_display_name;
readonly Property<double> _font_size;
readonly Property<bool> _show_predicate_animations, _show_cell_predicate_animations, _show_background_color;
readonly OrdersModel _orders_model;
class GridSettingsVM : ParameterGroupVM, IRequestViewClose, IWindowSettingsVM<GridSettings>, ITemplateSelectorKey {
readonly FontParameterVM _font_vm = new FontParameterVM("Cell Font");
readonly ColorParameterVM
_font_color = new ColorParameterVM("Cell Font Color"),
_selection_highlight = new ColorParameterVM("Selection Highlight"),
_background = new ColorParameterVM("Background");
readonly Property<GridColumnSettingsVM> _current_column;
readonly IReadOnlyDictionary<string, GridColumnSettingsFeatureFlags> _column_flags;
bool _background_enabled, _show_column_display_name;
IDisposable _dispose;
public GridSettingsVM(
IBus bus,
OrdersModel orders_model,
Func<TPredicateData> predicate_data_factory,
Func<TPredicateData, INotificationPredicate<TRowData>> notification_predicate_factory,
Func<IPropertyWrapperVM<TPredicateData>> create_predicate_vm,
Func<string, IPropertyWrapperVM<TPredicateData>> create_column_predicate_vm,
Func<GridSettings, IEnumerable<IPredicateAnimationsData<TRowData, TPredicateData>>, TSettingsData> create_settings_obj,
IReadOnlyDictionary<string, GridColumnSettingsFeatureFlags> column_flags = null) {
//
this.bus = bus;
_orders_model = orders_model;
_predicate_data_factory = predicate_data_factory;
_create_predicate_vm = create_predicate_vm;
_create_column_predicate_vm = create_column_predicate_vm;
_create_settings_obj = create_settings_obj;
protected GridSettingsVM(string name)
: base(name) { }
public GridSettingsVM(IReadOnlyDictionary<string, GridColumnSettingsFeatureFlags> column_flags = null)
: this("Grid Settings") {
_column_flags = column_flags ?? new Dictionary<string, GridColumnSettingsFeatureFlags>();
_font_family = backing(nameof(font_family), TextUtils.StandardFontFamily);
_font_size = backing(nameof(font_size), FontSizes.Small);
_font_weight = backing(nameof(font_weight), FontWeights.Normal);
_font_display_name = backing(nameof(font_display_name), () => TextUtils.display_name(font_family, font_size), new[] { nameof(font_family), nameof(font_size) });
_show_predicate_animations = backing(nameof(show_predicate_animations), true);
_show_cell_predicate_animations = backing(nameof(show_cell_predicate_animations), true);
_show_background_color = backing(nameof(show_background_color), false);
_current_column = backing<IColumnVM>(nameof(current_column));
predicate_animations = new ObservableEditableCollection<PredicateRowAnimationVM>(() =>
new PredicateRowAnimationVM(_create_predicate_vm(), _predicate_data_factory), true);
predicate_animations.CollectionChanged += WPFHelper.create_collection_changed_cb(on_items_added: (_, __) => _show_predicate_animations.set(true));
_current_column = backing<GridColumnSettingsVM>(nameof(current_column));
sync_params();
// font
_dispose = Disposable.Create(ObservableAutoWrapper.connect_item_hooks(columns, c => {
_font_vm.result_changed += on_result_changed;
_background.result_changed += on_result_changed;
_font_color.Changed += on_font_color_changed;
on_font_color_changed(default, _font_color.value);
on_result_changed();
return Disposable.Create(() => {
_font_vm.result_changed -= on_result_changed;
_background.result_changed -= on_result_changed;
_font_color.Changed -= on_font_color_changed;
});
// set the whole state everytime, bc it's simpler and less error prone
// than coordinating between specific properties.
void on_result_changed() {
c.background.default_value = _background.value;
c.font_weight.family = _font_vm.family;
c.font_weight.default_value = _font_vm.weight;
c.font_weight.size = _font_vm.size;
}
void on_font_color_changed(Color oldval, Color newval) =>
c.font_color.default_value = newval;
}));
}
public event EventHandler RequestClose;
public event Action committed;
public IBus bus { get; }
public ObservableCollection<IColumnVM> columns { get; } = new ObservableCollection<IColumnVM>();
public IColumnVM current_column { get => _current_column.Value; set => _current_column.set(value); }
public string font_display_name => _font_display_name.Value;
public FontFamily font_family { get => _font_family.Value; set => _font_family.set(value); }
public FontWeight font_weight { get => _font_weight.Value; set => _font_weight.set(value); }
public ColorPickerPopupVM font_color { get; } = new ColorPickerPopupVM();
public ColorPickerPopupVM selection_highlight { get; } = new ColorPickerPopupVM();
public ColorPickerPopupVM background_color { get; } = new ColorPickerPopupVM();
public bool show_background_color => _show_background_color.Value;
public double font_size { get => _font_size.Value; set => _font_size.set(value); }
public bool enable_sound_triggers => true;
public bool show_predicate_animations { get => _show_predicate_animations.Value; set => _show_predicate_animations.set(value); }
public bool show_cell_predicate_animations { get => _show_cell_predicate_animations.Value; set => _show_cell_predicate_animations.set(value); }
public ObservableEditableCollection<PredicateRowAnimationVM> predicate_animations { get; }
public ObservableCollection<GridColumnSettingsVM> columns { get; } = new ObservableCollection<GridColumnSettingsVM>();
public GridColumnSettingsVM current_column {
get => _current_column.Value;
set => _current_column.set(value);
}
public bool show_column_display_name {
get => _show_column_display_name;
set => Set(ref _show_column_display_name, value);
}
public IReadOnlyList<DialogSkirtItem> dialog_skirt_items => new[] {
new DialogSkirtItem("OK", new DelegateCommand(() => {
committed?.Invoke();
@@ -120,116 +112,82 @@ namespace mbts.spark {
public string key => ui.spark.GridSettingsTemplateKeys.SimpleGrid;
public TSettingsData get_result() {
var animation_map = predicate_animations
.Select(p => p.create())
.GroupBy(d => d.predicate)
.ToDictionary(g => g.Key, g => animation_tuple(
g.SelectMany(v => v.animations).ToList(),
g.SelectMany(v => v.sounds).ToList()));
public IDisposable begin_change_columns() {
var curr_col_id = current_column?.id;
return Disposable.Create(() => {
if (curr_col_id == null || !columns.TryGetFirst(c => c.id == curr_col_id, out var curr_column))
curr_column = columns.FirstOrDefault();
_current_column.set(curr_column);
});
}
public GridSettings get_result() {
var col_settings = new List<GridColumnSettings>();
foreach (var c in columns) {
var (settings, animations) = c.create();
var settings = c.create();
col_settings.Add(settings);
foreach (IGrouping<TPredicateData, CellAnimation> g in animations)
animation_map.AddOrUpdate(g.Key, p => animation_tuple(c: g), (p, t) => t.cell_animations.AddRange(g));
}
return _create_settings_obj(new GridSettings(font_family.name(), font_size, font_weight, font_color.color.rgb_color(), selection_highlight.to_proto(), col_settings,
background: background_color.color.with((byte)(MathUtils.within_range(0, 1, background_color.opacity) * 255.0)).rgb_color()),
animation_map.Select(kv => PredicateAnimations.create_data<TRowData, TPredicateData>(kv.Key, kv.Value.cell_animations, kv.Value.row_animations, kv.Value.sounds)));
//
(List<RowAnimation> row_animations, List<SoundNotification> sounds, List<CellAnimation> cell_animations) animation_tuple(IEnumerable<RowAnimation> r = null, IEnumerable<SoundNotification> s = null, IGrouping<TPredicateData, CellAnimation> c = null) =>
(r?.ToList() ?? new List<RowAnimation>(), s?.ToList() ?? new List<SoundNotification>(), c?.ToList() ?? new List<CellAnimation>());
return new GridSettings(
_font_vm.family?.name(),
_font_vm.size,
_font_vm.weight,
_font_color.value.rgb_color(),
new ColorOpacityProto(_selection_highlight.value.rgb_color()),
col_settings,
background: _background_enabled ? _background.value.rgb_color() : 0,
enable_background_color: _background_enabled);
}
public void load(TSettingsData sd) {
_font_family.set(new FontFamily(sd.general.font_family));
_font_weight.set(sd.general.font_weight);
font_color.load_from(sd.general.font_color.rgb_solid_brush());
selection_highlight.load_from(sd.general.selection_highlight.to_brush());
_show_background_color.set(sd.general.enable_background_color);
if (sd.general.enable_background_color) {
// note that when the OB is moved to allow background colors
// we can just background_color.color instead of load_from
// so that we get all the alpha goodness similar to SimpleGridSettingsVM
// assuming bind_opacity_to_alpha on the picker.
background_color.load_from(sd.general.background_color.argb_color().to_solid_brush());
}
_font_size.set(sd.general.font_size);
var col_animations = new Dictionary<string, Dictionary<TPredicateData, List<CellAnimation>>>();
predicate_animations.Clear();
foreach (var pa in sd.predicate_animations) {
if (pa.row_animations.Count > 0 || pa.sound_notifications.Count > 0) {
var vm = new PredicateRowAnimationVM(_create_predicate_vm(), _predicate_data_factory);
vm.load_from((pa.predicate_data, pa.row_animations, pa.sound_notifications));
predicate_animations.Add(vm);
}
foreach (var ca in pa.cell_animations) {
col_animations.AddOrUpdate(ca.column_id,
col => new Dictionary<TPredicateData, List<CellAnimation>> { { pa.predicate_data, new List<CellAnimation> { ca } } },
(col, d) => d.AddOrUpdate(pa.predicate_data, p => new List<CellAnimation> { ca }, (p, l) => l.Add(ca)));
public void load(GridSettings settings) =>
load(settings, true);
public void load(GridSettings settings, bool sync_columns) {
_font_vm.family = new FontFamily(settings.font_family);
_font_vm.weight = settings.font_weight;
_font_vm.size = settings.font_size;
_font_color.value = settings.font_color.rgb_color();
_selection_highlight.value = settings.selection_highlight.argb_color;
// note that when the OB is moved to allow background colors
// we can just background_color.color instead of load_from
// so that we get all the alpha goodness similar to SimpleGridSettingsVM
// assuming bind_opacity_to_alpha on the picker.
_background_enabled = settings.enable_background_color;
_background.value = settings.background;
if (sync_columns) {
using (begin_change_columns()) {
columns.SetItems(settings.columns.Select(col => {
var vm = new GridColumnSettingsVM();
vm.load_from(col);
// reapply our favorite column flags
// this comes in handy when performing Load From Factory - the editor will contain
// a consistent view of what can be overridden and what cannot
if (_column_flags.TryGetValue(vm.id, out GridColumnSettingsFeatureFlags flags))
vm.apply_flags(flags);
return vm;
}));
}
}
var curr_col_id = current_column?.id;
columns.SetItems(sd.general.columns.Select(col => {
var vm = new ColumnVM(this);
vm.load_from((col, col_animations.GetValueOrDefault(col.id)?.ToFlatLookup(kv => kv.Key, kv => kv.Value)));
// reapply our favorite column flags
// this comes in handy when performing Load From Factory - the editor will contain
// a consistent view of what can be overridden and what cannot
if (_column_flags.TryGetValue(vm.id, out GridColumnSettingsFeatureFlags flags))
vm.apply_flags(flags);
return vm;
}));
if (curr_col_id == null || !columns.TryGetFirst(c => c.id == curr_col_id, out IColumnVM curr_column))
curr_column = columns.FirstOrDefault();
_current_column.set(curr_column);
sync_params();
}
public class PredicateRowAnimationVM : DisposableViewModelBase, IValueFactory<(TPredicateData predicate, IEnumerable<RowAnimation> animations, IEnumerable<SoundNotification> sounds)> {
readonly Property<bool> _show_animations/*, _play_sound*/;
readonly Func<TPredicateData> _predicate_state_factory;
public IObservable<Color> subscribe_background() =>
_background.SubscribeValue(true);
public PredicateRowAnimationVM(IPropertyWrapperVM<TPredicateData> predicate, Func<TPredicateData> predicate_state_factory) {
this.predicate = predicate;
_predicate_state_factory = predicate_state_factory;
_show_animations = backing(nameof(show_animations), true);
//_play_sound = backing(nameof(play_sound), true);
animations = new ObservableEditableCollection<RowAnimationVM>(() => {
var vm = new RowAnimationVM();
vm.selected_animation_type = vm.animation_types.FirstOrDefault();
return vm;
}, true);
animations.CollectionChanged += WPFHelper.create_collection_changed_cb(on_items_added: (_, __) => _show_animations.set(true));
_dispose = predicate as IDisposable;
}
public IObservable<Color> subscribe_font_color() =>
_font_color.SubscribeValue(true);
public IPropertyWrapperVM<TPredicateData> predicate { get; }
public ObservableEditableCollection<RowAnimationVM> animations { get; }
public IEnumerable<CellAnimation.AnimationTypeOneofCase> animation_types { get; } = EnumHelper.get_values_except(CellAnimation.AnimationTypeOneofCase.None);
public bool show_animations { get => _show_animations.Value; set => _show_animations.set(value); }
//public bool play_sound { get => _play_sound.Value; set => _play_sound.set(value); }
public IPropertyWrapperVM<SoundNotification> sound { get; } = SoundAnimationVM.create_system_sound_vm();
public IObservable<(FontFamily family, FontWeight weight, double size)> subscribe_font() =>
_font_vm.SubscribeValue(true);
public (TPredicateData predicate, IEnumerable<RowAnimation> animations, IEnumerable<SoundNotification> sounds) create() =>
(predicate.create_assigned(_predicate_state_factory),
animations.Select(a => a.create_assigned()),
new[] { sound.create_assigned() });
//EnumerableExtensions.return_if(play_sound, () => sound.create_assigned()));
public void load_from((TPredicateData predicate, IEnumerable<RowAnimation> animations, IEnumerable<SoundNotification> sounds) args) {
predicate.load_from(args.predicate);
animations.SetItems(args.animations.Select(a => {
var vm = new RowAnimationVM();
vm.load_from(a);
return vm;
}));
var en = args.sounds.GetEnumerator();
if (en.MoveNext()) {
//_play_sound.set(true);
sound.load_from(en.Current);
} //else _play_sound.set(false);
}
public virtual void sync_params() {
var fonts_and_colors = HeaderedParameterGroupVM.create("Fonts/Colors", _font_vm, _font_color);
if (_background_enabled)
fonts_and_colors.parameters.Add(_background);
fonts_and_colors.parameters.Add(_selection_highlight);
parameters.SetItems(fonts_and_colors);
}
public void Dispose() =>
Disposable.dispose(ref _dispose);
}
}
File diff suppressed because it is too large. Load diff
@@ -49,6 +49,8 @@ namespace mbts.ui.controls
};
}
public bool SelectItemOnDrop { get; set; }
private DragIndicatorAdorner<DraggableListBoxItem> _adorner;
private IDisposable _drag_operation;
@@ -56,9 +58,13 @@ namespace mbts.ui.controls
protected override DependencyObject GetContainerForItemOverride() {
var container = new DraggableListBoxItem();
container.MouseEnter += (s, e) => _adorner.InvalidateVisual();
container.MouseLeave += (s, e) => _adorner.InvalidateVisual();
container.MouseEnter += on_mouse_changed;
container.MouseLeave += on_mouse_changed;
container.MouseMove += on_mouse_changed;
return container;
//
void on_mouse_changed(object sender, MouseEventArgs e) =>
_adorner.InvalidateVisual();
}
protected override void OnPreviewMouseLeftButtonDown(MouseButtonEventArgs e) {
@@ -89,6 +95,8 @@ namespace mbts.ui.controls
if (source != null && target != null) {
CoerceValue(DragDropManagerProperty);
DragDropManager.Drop(source, target.Content);
if (SelectItemOnDrop && target is ListBoxItem lbi)
lbi.SetCurrentValue(ListBoxItem.IsSelectedProperty, true);
}
}
@@ -1,6 +1,6 @@
using mbts.ui.utility;
using bts.utils;
using mbts.ui.utility;
using mbts.ui.utility.text;
using bts.utils;
using System;
using System.Collections;
using System.Globalization;
@@ -9,7 +9,6 @@ using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Media;
using FontFamily = System.Windows.Media.FontFamily;
namespace mbts.ui.controls {
/// <summary>
@@ -20,13 +19,8 @@ namespace mbts.ui.controls {
public FontPicker() {
InitializeComponent();
var families = Fonts.SystemFontFamilies.ToList();
families.Sort(CompareUtils.safe_comparison<FontFamily>(
(a, b) => a.FamilyNames.First().Value.CompareTo(b.FamilyNames.First().Value)));
SetCurrentValue(FontFamiliesProperty, families);
var weights = TextUtils.all_font_weights().ToList();
weights.Sort((x, y) => x.ToOpenTypeWeight().CompareTo(y.ToOpenTypeWeight()));
SetCurrentValue(FontWeightsProperty, weights);
SetCurrentValue(FontFamiliesProperty, TextUtils.SystemFontFamilies);
SetCurrentValue(FontWeightsProperty, TextUtils.SystemFontWeights);
Loaded += (s, e) => _font_family_list_box.ScrollIntoView(SelectedFontFamily);
}
@@ -1,8 +1,10 @@
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:c="clr-namespace:mbts.ui.controls"
xmlns:usp="clr-namespace:mbts.ui.spark"
xmlns:uspcv="clr-namespace:mbts.ui.spark.converters">
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:c="clr-namespace:mbts.ui.controls"
xmlns:usp="clr-namespace:mbts.ui.spark"
xmlns:uspcv="clr-namespace:mbts.ui.spark.converters"
>
<uspcv:OrderTypeBrushConverter x:Key="OrderTypeBrushConverter"/>
<uspcv:OrderTypeDisplayTextConverter x:Key="OrderTypeDisplayTextConverter"/>
<Style TargetType="usp:OrderTypeItem">
@@ -26,15 +28,17 @@
<ColumnDefinition Width="Auto"/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<c:Badge
<Rectangle
HorizontalAlignment="Left"
VerticalAlignment="Center"
Background="{Binding BadgeFill, RelativeSource={RelativeSource TemplatedParent}}"
BorderThickness="1"
Fill="{Binding BadgeFill, RelativeSource={RelativeSource TemplatedParent}}"
Stroke="Black"
StrokeThickness="1"
Width="15"
Height="10"
Focusable="False"
IsTabStop="False"
RadiusX="{x:Static c:Badge.DefaultCornerRadius}"
RadiusY="{x:Static c:Badge.DefaultCornerRadius}"
/>
<TextBlock
x:Name="text"
@@ -10,9 +10,18 @@ namespace mbts.ui.controls {
/// Interaction logic for PopupControl.xaml
/// </summary>
public class PopupControl : HeaderedContentControl {
IDisposable _popup_hook;
Popup _popup;
Button _button;
static PopupControl() =>
DefaultStyleKeyProperty.OverrideMetadata(typeof(PopupControl), new FrameworkPropertyMetadata(typeof(PopupControl)));
public PopupControl() {
CommandBindings.Add(new CommandBinding(OpenCommand, (s, e) => IsOpen = true));
CommandBindings.Add(new CommandBinding(CloseCommand, (s, e) => IsOpen = false));
}
public static readonly RoutedCommand
OpenCommand = new RoutedCommand("Open", typeof(PopupControl)),
CloseCommand = new RoutedCommand("Close", typeof(PopupControl));
@@ -23,6 +32,16 @@ namespace mbts.ui.controls {
public event RoutedEventHandler Opened { add => AddHandler(OpenedEvent, value); remove => RemoveHandler(OpenedEvent, value); }
public event RoutedEventHandler Closed { add => AddHandler(ClosedEvent, value); remove => RemoveHandler(ClosedEvent, value); }
public static readonly DependencyProperty ButtonStyleProperty =
WPFHelper.create_dp<Style, PopupControl>(
nameof(ButtonStyle),
(o, v) => WPFHelper.apply_style(ref o._button, v));
public Style ButtonStyle {
get => GetValue(ButtonStyleProperty) as Style;
set => SetValue(ButtonStyleProperty, value);
}
#region PopupPlacement
public static readonly DependencyProperty PopupPlacementProperty = DependencyProperty.Register("PopupPlacement", typeof(PlacementMode), typeof(PopupControl), new PropertyMetadata(PlacementMode.Right, OnPopupPlacementChanged));
private static void OnPopupPlacementChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { d.SetValue(IsOpenTransformPropertyKey, _get_is_open_transform((PlacementMode)e.NewValue)); }
@@ -105,18 +124,12 @@ namespace mbts.ui.controls {
}
#endregion
public PopupControl() {
CommandBindings.Add(new CommandBinding(OpenCommand, (s, e) => IsOpen = true));
CommandBindings.Add(new CommandBinding(CloseCommand, (s, e) => IsOpen = false));
}
IDisposable _popup_hook;
Popup _popup;
Button _button;
public override void OnApplyTemplate() {
Disposable.dispose(ref _popup_hook);
// button
_button = (Button)GetTemplateChild("button");
WPFHelper.apply_style(ref _button, ButtonStyle);
// popup
_popup = (Popup)GetTemplateChild("popup");
_popup.HorizontalOffset = HorizontalOffset;
_popup.VerticalOffset = VerticalOffset;
@@ -1,9 +1,29 @@
using System;
using System.Windows;
using System.Windows.Controls.Primitives;
using System.Windows.Input;
namespace mbts.ui.controls {
static class PopupViewController {
#region ButtonSource
public static readonly DependencyProperty ButtonSourceProperty =
DependencyProperty.RegisterAttached("ButtonSource", typeof(ButtonBase), typeof(PopupViewController),
new PropertyMetadata(OnButtonSourceChanged));
static void OnButtonSourceChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) {
if (d is Popup popup)
popup.SetValue(ButtonSourceDependencyPropertyKey,
e.NewValue is ButtonBase button ? attach(popup, button) : null);
}
public static bool GetButtonSource(DependencyObject d) => (bool)d.GetValue(ButtonSourceProperty);
public static void SetButtonSource(DependencyObject d, bool value) => d.SetValue(ButtonSourceProperty, value);
static readonly DependencyPropertyKey ButtonSourceDependencyPropertyKey =
DependencyProperty.RegisterAttachedReadOnly("ButtonSourceDependency", typeof(IDisposable), typeof(PopupViewController),
new PropertyMetadata(FrameworkElementLifeDependencyHelper.DisposeOld));
#endregion
public static IDisposable attach(Popup popup, ButtonBase open_cmd_source, bool auto_focus_popup_root_on_open = false) {
/// There is a paradoxical corner case handled here that occurs when the user clicks the button to 'close' the popup.
/// Conventional logic might dictate that a <see cref="ToggleButton "/> be used to bind <see cref="ToggleButton.IsChecked"/> to <see cref="Popup.IsOpen"/>,
@@ -55,6 +55,7 @@
<SolidColorBrush Color="{StaticResource MouseOverBorderLightColor}"/>
</Setter.Value>
</Setter>
<Setter Property="Background" Value="{u:BTSColor UI.Button.mouseover_background}"/>
</Trigger>
<Trigger Property="IsEnabled" Value="False">
<Setter Property="Opacity" Value="0.5"/>
@@ -62,6 +63,67 @@
</Style.Triggers>
</Style>
<Style x:Key="SparkToolBarButton" TargetType="Button">
<Setter Property="ToolTipService.ShowOnDisabled" Value="True"/>
<Setter Property="BorderThickness" Value="1"/>
<Setter Property="Height" Value="22"/>
<Setter Property="HorizontalContentAlignment" Value="Stretch"/>
<Setter Property="Padding" Value="2"/>
<Setter Property="VerticalContentAlignment" Value="Stretch"/>
<Setter Property="Width" Value="22"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Button">
<Border
x:Name="Border"
BorderThickness="{TemplateBinding BorderThickness}"
Padding="{TemplateBinding Padding}"
Height="{TemplateBinding Height}"
Width="{TemplateBinding Width}"
SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"
Opacity="1"
>
<Border.BorderBrush>
<SolidColorBrush/>
</Border.BorderBrush>
<Border.Background>
<SolidColorBrush/>
</Border.Background>
<VisualStateManager.VisualStateGroups>
<VisualStateGroup Name="CommonStates">
<VisualState Name="Normal"/>
<VisualState Name="MouseOver">
<Storyboard>
<ColorAnimation Storyboard.TargetProperty="Background.Color" BeginTime="0" Duration="0" To="{DynamicResource Control.AlphaLightMediumGrayColor}"/>
<ColorAnimation Storyboard.TargetProperty="BorderBrush.Color" BeginTime="0" Duration="0" To="{DynamicResource Control.DarkGrayColor}"/>
</Storyboard>
</VisualState>
<VisualState u:StateManager.Name="Pressed">
<Storyboard>
<ColorAnimation Storyboard.TargetProperty="Background.Color" BeginTime="0" Duration="0" To="{DynamicResource Control.AlphaMediumGrayColor}"/>
<ColorAnimation Storyboard.TargetProperty="BorderBrush.Color" BeginTime="0" Duration="0" To="{DynamicResource Control.DarkGrayColor}"/>
</Storyboard>
</VisualState>
<VisualState Name="Disabled">
<Storyboard>
<DoubleAnimation Storyboard.TargetProperty="Opacity" BeginTime="0" Duration="0" To="0.52"/>
</Storyboard>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
<ContentPresenter
Name="presenter"
Grid.Column="1"
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
RecognizesAccessKey="True"
/>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<Style x:Key="ToolBarThumbStyle" TargetType="{x:Type Thumb}">
<Setter Property="OverridesDefaultStyle" Value="True"/>
<Setter Property="Cursor" Value="SizeAll"/>
@@ -161,7 +223,7 @@
<Setter Property="HorizontalAlignment" Value="Stretch"/>
<Setter Property="SnapsToDevicePixels" Value="True"/>
<Setter Property="OverridesDefaultStyle" Value="True"/>
<Setter Property="ItemContainerStyle" Value="{StaticResource SparkToolBarItem}"/>
<!--<Setter Property="ItemContainerStyle" Value="{StaticResource SparkToolBarItem}"/>-->
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ToolBar}">
@@ -1,7 +1,10 @@
using bts.spark;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Windows.Data;
using System.Windows.Media;
namespace mbts.ui.converters {
static class ValueConverter {
@@ -25,8 +28,12 @@ namespace mbts.ui.converters {
public static readonly IValueConverter
DoubleTextConverter = new DoubleTextConverter(),
DoubleGreaterThanZero = new DoubleGreaterThanConverter { Value = 0 },
GeometryParser = new TypeConverterWrapper {
TypeConverter = new GeometryConverter()
},
IntTextConverter = new IntTextConverter(),
UIntTextConverter = new UIntTextConverter(),
NotNull = ValueConverter.create(obj => obj != null),
QuantityMeasureTextConverter = ValueConverter.create<QuantityMeasureImmutable?, string>(fmt),
SecuritySelectorText,
SimpleString = new AnonymousValueConverter(obj => obj?.ToString()),
@@ -40,7 +47,12 @@ namespace mbts.ui.converters {
}
public static readonly IMultiValueConverter
AreEqualConverter = new AreEqualConverter();
AreEqualConverter = new AreEqualConverter(),
SliderPercentage = ValueConverter.create<double, double, double, double>((value, min, max) =>
DoubleHelper.AnyNaN(value, min, max) || min >= max ?
double.NaN :
(value - min) / (max - min)),
Any = new AnonymousMultiValueConverter(objs => objs.OfTypeSafe<bool>().Any(_ => _), null);
public static string fmt(QuantityMeasureImmutable? qm) {
if (qm.HasValue) {
@@ -80,6 +92,22 @@ namespace mbts.ui.converters {
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) { return _convert_back(ConvertUtils.try_convert<TOut>(value)); }
}
class AnonymousMultiValueConverter : IMultiValueConverter {
readonly Func<object[], object> _convert;
readonly Func<object, object[]> _convert_back;
public AnonymousMultiValueConverter(Func<object[], object> convert, Func<object, object[]> convert_back) {
_convert = convert;
_convert_back = convert_back;
}
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture) =>
_convert(values);
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture) =>
_convert_back == null ? throw new InvalidOperationException() : _convert_back(value);
}
public class AnonymousMultiValueConverter<TIn1, TIn2, TOut> : IMultiValueConverter {
readonly Func<TIn1, TIn2, TOut> _convert;
@@ -2,9 +2,10 @@
using System.Globalization;
using System.Text;
using System.Windows.Data;
using System.Windows.Markup;
namespace mbts.ui.converters {
class AppendTextConverter : IValueConverter {
class AppendTextConverter : MarkupExtension, IValueConverter {
public string Prefix { get; set; }
public string Suffix { get; set; }
@@ -18,5 +19,8 @@ namespace mbts.ui.converters {
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) =>
throw new NotImplementedException();
public override object ProvideValue(IServiceProvider serviceProvider) =>
this;
}
}
@@ -0,0 +1,92 @@
using mbts.utils;
using System;
using System.Collections;
using System.Globalization;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
namespace mbts.ui.converters {
class ComparisonConverter : IValueConverter {
public object ValueForGreaterThan { get; set; } = 1;
public object ValueForLessThan { get; set; } = -1;
public object ValueForEqual { get; set; }
public object Comparand { get; set; }
public IComparer Comparer { get; set; }
public object Convert(object value, Type targetType, object parameter, CultureInfo culture) =>
convert(value, Comparand, Comparer, ValueForLessThan, ValueForGreaterThan, ValueForEqual);
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) =>
convert(value, Comparand, Comparer, ValueForGreaterThan, ValueForLessThan, ValueForEqual);
public static object convert(object value, object comparand, IComparer comparer, object value_for_less_than, object value_for_greater_than, object value_for_equal_to) {
int result = compare(value, comparand, comparer);
if (result < 0)
return value_for_less_than;
if (result > 0)
return value_for_greater_than;
return value_for_equal_to;
}
public static int compare(object value, object comparand, IComparer comparer) {
if (comparer != null) {
return comparer.Compare(value, comparand);
} else if (value is IComparable comparable) {
return comparable.CompareTo(comparand);
} else {
return System.Collections.Comparer.Default.Compare(value, comparand);
}
}
}
class IsLessThanConverter : ComparisonConverter {
public IsLessThanConverter() {
ValueForGreaterThan = false;
ValueForEqual = false;
ValueForLessThan = true;
}
}
class IsGreaterThanConverter : ComparisonConverter {
public IsGreaterThanConverter() {
ValueForLessThan = false;
ValueForEqual = false;
ValueForGreaterThan = true;
}
}
class DynamicComparisonConverter : IMultiValueConverter {
public object ValueForGreaterThan { get; set; } = 1;
public object ValueForLessThan { get; set; } = -1;
public object ValueForEqualTo { get; set; }
public IComparer Comparer { get; set; }
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture) {
ExceptionAssert.array_length(values, 2, nameof(values));
if (values.Contains(DependencyProperty.UnsetValue))
return new ValidationResult(false, null);
return ComparisonConverter.convert(values[0], values[1], Comparer, ValueForLessThan, ValueForGreaterThan, ValueForEqualTo);
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture) =>
throw new NotSupportedException();
}
class DynamicIsLessThanConverter : DynamicComparisonConverter {
public DynamicIsLessThanConverter() {
ValueForLessThan = true;
ValueForEqualTo = true;
ValueForGreaterThan = false;
}
}
class DynamicIsGreaterThanConverter : DynamicComparisonConverter {
public DynamicIsGreaterThanConverter() {
ValueForLessThan = false;
ValueForEqualTo = false;
ValueForGreaterThan = true;
}
}
}
@@ -0,0 +1,46 @@
using bts.utils;
using mbts.ui.utility.text;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Text;
using System.Windows;
using System.Windows.Data;
namespace mbts.ui.converters {
class FontWeightDisplayTextConverter : IValueConverter {
// TODO (when there reallllly isn't anything else to do):
// come up with interpolation naming system (e.g., if weight is between 'Light' and 'Super Light,' name it 'Semi Light');
// below is the beginning of such an effort.
//static SortedList<FontWeight, string> _font_weight_cache =
// new SortedList<FontWeight, string>(Comparer<int>.Default.wrap<int, FontWeight>(fw => fw.ToOpenTypeWeight()));
//static FontWeightDisplayTextConverter() {
// foreach (var fw in TextUtils.SystemFontWeights)
// _font_weight_cache.Add(fw, fw.ToString());
//}
public object Convert(object value, Type targetType, object parameter, CultureInfo culture) {
if (value is FontWeight fw) {
string rv = fw.ToString();
if (int.TryParse(rv, out var open_type_weight)) {
// msoft doesn't have a pretty name
rv = _fallback_name(open_type_weight);
}
return rv;
}
return null;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) =>
throw new NotImplementedException();
static string _fallback_name(int open_type_weight) {
switch (open_type_weight) {
case 350: return "Semi Light";
default: return open_type_weight.ToString();
}
}
}
}
@@ -5,11 +5,11 @@ using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Media;
namespace mbts.ui.converters
{
namespace mbts.ui.converters {
[ValueConversion(typeof(ImageSource), typeof(Image))]
internal class ImageSourceIconConverter : IValueConverter
{
class ImageSourceIconConverter : IValueConverter {
public static ImageSourceIconConverter Instance => new ImageSourceIconConverter();
public Thickness Margin { get; set; } = new Thickness(0);
public double Width { get; set; } = double.NaN;
public double Height { get; set; } = double.NaN;
@@ -0,0 +1,24 @@
using System;
using System.Globalization;
using System.Windows.Controls;
using System.Windows.Data;
namespace mbts.ui.converters {
[ValueConversion(typeof(int), typeof(int))]
class IntAdditionConverter : IValueConverter {
public int Value { get; set; }
public object Convert(object value, Type targetType, object parameter, CultureInfo culture) {
if (value is int i)
return i + Value;
return new ValidationResult(false, "Not an integer.");
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) {
if (value is int i)
return i - Value;
return new ValidationResult(false, "Not an integer.");
}
}
}
@@ -0,0 +1,16 @@
using System;
using System.ComponentModel;
using System.Globalization;
using System.Windows.Data;
namespace mbts.ui.converters {
class TypeConverterWrapper : IValueConverter {
public TypeConverter TypeConverter { get; set; }
public object Convert(object value, Type targetType, object parameter, CultureInfo culture) =>
TypeConverter.ConvertFrom(null, culture, value);
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) =>
TypeConverter.ConvertTo(null, culture, value, targetType);
}
}
@@ -13,12 +13,21 @@ namespace mbts.ui.converters {
public IList Map => _map;
public object Default { get; set; }
public IValueConverter DefaultConverter { get; set; }
public object Convert(object value, Type targetType, object parameter, CultureInfo culture) =>
_map.TryGetValue(value ?? KeyValue.NullPlaceholder, out var kv) ? kv.Value : Default;
_map.TryGetValue(value ?? KeyValue.NullPlaceholder, out var kv) ?
kv.Value :
DefaultConverter == null ?
Default :
DefaultConverter.Convert(value, targetType, parameter, culture);
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) =>
_map.TryGetFirst(kv => Equals(kv.Value, value), out KeyValue result) ? result.Key : Default;
_map.TryGetFirst(kv => Equals(kv.Value, value), out KeyValue result) ?
result.Key :
DefaultConverter == null ?
Default :
DefaultConverter.ConvertBack(value, targetType, parameter, culture);
}
class KeyValue {
@@ -0,0 +1,56 @@
using System;
using System.Globalization;
using System.Windows;
using System.Windows.Data;
namespace mbts.ui.converters {
[ValueConversion(typeof(SizeToContent), typeof(double))]
abstract class WindowSizeThresholdConverter : IValueConverter {
readonly SizeToContent _size_to_content_flag;
protected WindowSizeThresholdConverter(SizeToContent size_to_content_flag) =>
_size_to_content_flag = size_to_content_flag;
public double SizeToContentThreshold { get; set; } = double.NaN;
public double Threshold { get; set; }
public object Convert(object value, Type targetType, object parameter, CultureInfo culture) {
if (value is SizeToContent stc) {
if ((stc & _size_to_content_flag) == _size_to_content_flag) {
if (double.IsNaN(SizeToContentThreshold)) {
if (parameter is double d)
return d;
} else {
return SizeToContentThreshold;
}
}
}
return Threshold;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) =>
throw new NotSupportedException();
}
[ValueConversion(typeof(SizeToContent), typeof(double))]
sealed class WindowMinWidthConverter : WindowSizeThresholdConverter {
public WindowMinWidthConverter() : base(SizeToContent.Width) { }
}
[ValueConversion(typeof(SizeToContent), typeof(double))]
sealed class WindowMaxWidthConverter : WindowSizeThresholdConverter {
public WindowMaxWidthConverter() : base(SizeToContent.Width) =>
Threshold = double.PositiveInfinity;
}
[ValueConversion(typeof(SizeToContent), typeof(double))]
sealed class WindowMinHeightConverter : WindowSizeThresholdConverter {
public WindowMinHeightConverter() : base(SizeToContent.Height) { }
}
[ValueConversion(typeof(SizeToContent), typeof(double))]
sealed class WindowMaxHeightConverter : WindowSizeThresholdConverter {
public WindowMaxHeightConverter() : base(SizeToContent.Height) =>
Threshold = double.PositiveInfinity;
}
}
@@ -156,6 +156,62 @@ namespace mbts.ui {
}
}
public static IDisposable connect_vertical_insertion_adorner(ItemsControl container, FrameworkElement root = null) {
VerticalInsertionAdorner insertion_adorner = null;
UIElement target = null;
VerticalInsertionAdorner.TargetSide target_side = VerticalInsertionAdorner.TargetSide.Top;
if (root == null)
root = container;
return Disposable.Create(
root.SubscribePreviewDragEnter(on_drag_enter),
root.SubscribePreviewDragLeave((s, e) => remove_adorner()),
root.SubscribePreviewDragOver(on_drag_over),
root.SubscribePreviewDrop((s, e) => remove_adorner()),
Disposable.Create(remove_adorner));
void on_drag_over(object sender, DragEventArgs e) {
_determine_drop_target(e);
_update_insertion_adorner();
e.Effects = DragDropEffects.Move;
e.Handled = true;
}
void on_drag_enter(object sender, DragEventArgs e) {
_determine_drop_target(e);
_update_insertion_adorner();
e.Effects = e.AllowedEffects;
}
// Draw an indicator where the drop will insert the item
void _update_insertion_adorner() {
if (insertion_adorner == null) {
insertion_adorner = new VerticalInsertionAdorner(target, target_side);
} else insertion_adorner.Side = target_side;
}
void _determine_drop_target(DragEventArgs e) {
var orig_target = target;
var orig_side = target_side;
target = null;
var pt = e.GetPosition(container);
pt.X = MathUtils.within_range(0, container.ActualWidth - 2, pt.X);
pt.Y = MathUtils.within_range(0, container.ActualHeight - 2, pt.Y);
if (container.InputHitTest(pt) is DependencyObject hit) {
target = container.ContainerFromElement(hit) as UIElement;
if (target != null) {
Point target_position = e.GetPosition(target);
target_side = target_position.Y < (target.RenderSize.Height / 2) ?
VerticalInsertionAdorner.TargetSide.Top :
VerticalInsertionAdorner.TargetSide.Bottom;
if (target != orig_target || target_side != orig_side)
remove_adorner();
}
}
}
void remove_adorner() {
if (insertion_adorner != null) {
insertion_adorner.Detach();
insertion_adorner = null;
}
}
}
[Conditional("DEBUG")]
private static void log(string message, params object[] args) {
Console.WriteLine(message, args);
@@ -1,10 +1,12 @@
using mbts.utils;
using bts;
using mbts.utils;
using System;
using System.Reactive.Linq;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Data;
using CompositeDisposable = System.Reactive.Disposables.CompositeDisposable;
namespace mbts.ui.extensions {
@@ -81,6 +83,33 @@ namespace mbts.ui.extensions {
}
}
public static IObservable<bool> ObserveIsSelected(this TabItem tab_item) {
int subscription_count = 0;
BindingProxy proxy = null;
return Observable.Create<bool>(o => {
if (subscription_count++ == 0) {
Log.assert(proxy == null, "Potential memory leak detected: binding proxy should be null when there are 0 subscriptions.");
proxy = new BindingProxy();
proxy.SetBinding(BindingProxy.ValueProperty, new Binding(nameof(TabItem.IsSelected)) { Source = tab_item });
}
bool last_observed = tab_item.IsSelected;
o.OnNext(last_observed);
proxy.ValueChanged += on_value_changed;
return Disposable.CreateDisposeOnce(dispose);
void dispose() {
proxy.ValueChanged -= on_value_changed;
if (--subscription_count == 0) {
Log.assert(proxy != null, "Binding proxy should not be null when subscriptions are active.");
proxy = null;
}
}
void on_value_changed(object d, DependencyPropertyChangedEventArgs e) {
if ((e.NewValue is bool b && b) != last_observed)
o.OnNext(last_observed = !last_observed);
}
});
}
public static IObservable<bool> ObserveIsChecked(this ToggleButton toggle_button) =>
Observable.Create<bool>(o => {
toggle_button.Checked += on_checked;
@@ -1,10 +1,14 @@
using mbts.config;
using mbts.ui.controls;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Windows;
using System.Windows.Forms;
using System.Windows.Media;
using Point = System.Windows.Point;
namespace mbts.ui.dpi {
public static class HighDpiExtensions {
@@ -119,5 +123,46 @@ namespace mbts.ui.dpi {
}
}
#endregion
/// <summary>
/// Calculates the value to be used for layout rounding at high DPI.
/// </summary>
/// <param name="value">Input value to be rounded.</param>
/// <param name="dpiScale">Ratio of screen's DPI to layout DPI</param>
/// <returns>Adjusted value that will produce layout rounding on screen at high dpi.</returns>
/// <remarks>This is a layout helper method. It takes DPI into account and also does not return
/// the rounded value if it is unacceptable for layout, e.g. Infinity or NaN. It's a helper associated with
/// UseLayoutRounding property and should not be used as a general rounding utility.</remarks>
public static double RoundLayoutValue(this double value) =>
RoundLayoutValue(value, dpi_ratio);
public static double RoundLayoutValue(double value, double dpiScale) {
if (DoubleUtil.AreClose(dpiScale, 1.0))
return Math.Round(value);
double result = Math.Round(value * dpiScale) / dpiScale;
return DoubleHelper.IsNaNOrInfinity(result) || DoubleUtil.AreClose(result, double.MaxValue) ? value : result;
}
public static Point RoundToLayout(this Point point) =>
new Point(RoundLayoutValue(point.X), RoundLayoutValue(point.Y));
public static Thickness RoundToLayout(this Thickness thickness) =>
new Thickness(
thickness.Left.RoundLayoutValue(),
thickness.Top.RoundLayoutValue(),
thickness.Right.RoundLayoutValue(),
thickness.Bottom.RoundLayoutValue());
public static GuidelineSet AntiAliasGuidelines(this Rect rect) =>
rect.AntiAliasGuidelines(dpi_ratio);
public static GuidelineSet AntiAliasGuidelines(this Rect rect, double pen_thickness) {
var half_pen_thickness = pen_thickness / 2;
//half_pen_thickness = 0;
var guidelines = new GuidelineSet(
new[] { rect.Left.RoundLayoutValue() - half_pen_thickness, rect.Right.RoundLayoutValue() + half_pen_thickness },
new[] { rect.Top.RoundLayoutValue() - half_pen_thickness, rect.Bottom.RoundLayoutValue() + half_pen_thickness });
return guidelines;
}
}
}
@@ -2,17 +2,12 @@
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Markup;
using System.Windows.Media;
namespace mbts.ui
{
namespace mbts.ui {
[MarkupExtensionReturnType(typeof(Brush))]
public class BrushExtension : MarkupExtension
{
class BrushExtension : MarkupExtension {
public BrushExtension() : this(Colors.Black) { }
public BrushExtension(Color Color) : this(Color, 1.0) { }
public BrushExtension(Color Color, double Opacity) { this.Color = Color; this.Opacity = Opacity; }
@@ -27,6 +22,70 @@ namespace mbts.ui
}
}
[MarkupExtensionReturnType(typeof(Brush))]
class FrozenBrush : MarkupExtension {
[ThreadStatic]
static Dictionary<BrushKey, SolidColorBrush> _brushes;
double _opacity = 1;
public FrozenBrush(Color color) =>
Color = color;
public Color Color { get; set; }
public double Opacity {
get => _opacity;
set => _opacity = value < 0 || value > 1 ?
throw new ArgumentException("Must be between 0 and 1 (inclusive).", nameof(value)) :
value;
}
public static SolidColorBrush get_brush(Color color, double opacity = 1.0) {
if (_brushes == null)
_brushes = new Dictionary<BrushKey, SolidColorBrush>();
unchecked {
ushort opac = opacity == 1 ? ushort.MaxValue : (ushort)(opacity * 100_000);
// color has a perfect hash (it is 32 bits, so it damn well better).
var key = new BrushKey(color.GetHashCode(), opac);
if (!_brushes.TryGetValue(key, out var brush)) {
_brushes[key] = brush = new SolidColorBrush(color).frozen();
}
return brush;
}
}
public override object ProvideValue(IServiceProvider serviceProvider) =>
get_brush(Color, Opacity);
readonly struct BrushKey : IEquatable<BrushKey> {
readonly int _color;
// bc opacity does not need more precision than 16 bits; 100,000 discrete values is more than enough
// (65,536 should avoid has collisions stemming from opacity; also leaves 16 bits to encode color).
readonly ushort _opacity;
public BrushKey(int color, ushort opacity) {
_color = color;
_opacity = opacity;
}
public bool Equals(BrushKey other) =>
_color == other._color &&
_opacity == other._opacity;
public override bool Equals(object obj) =>
obj is BrushKey other && Equals(other);
public override int GetHashCode() =>
// ushort.MaxValue indicates opacity of '1.'
// The value we have is the actual opacity multiplied by 100,000;
// so 1 would actually be a very small opacity: 0.0001.
_opacity == ushort.MaxValue ?
// the last bit indicates if opacity is zero
_color | -1 :
((ushort)_color | (_opacity << 16) | -1) ^ -1;
}
}
public class ColorTypeConverter : TypeConverter
{
public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType) { return sourceType == typeof(string); }
Loaded 100 of 140 files, more files were not shown because too many files have changed in this diff. Show more