Compare commits

...
Author SHA1 Message Date
John Colvin d421fdb41f Checkpoint - make pnl grid settings pretty (should rebase/clean up) 2019-10-21 12:00:47 -05:00
John Colvin 6cbff72bc3 make time and sales settings pretty 2019-10-19 18:04:39 -05:00
John Colvin d81f5b87fc fix pretty (again damnit) 2019-10-19 18:02:55 -05:00
John Colvin fe6f4ffa92 make mview settings pretty 2019-10-19 17:55:50 -05:00
John Colvin 7841faafa2 hook up HyperlinkColumn.font_weight binding 2019-10-19 17:54:47 -05:00
John Colvin 89df89db11 HyperlinkColumn should bind to HorizontalContentAlignment prop (not HorizontalAlignment) 2019-10-19 17:54:32 -05:00
John Colvin 03867914cb fix WPFHelper.measure_visual_content - 'left edge' calculation for right align; 'bottom edge' for bottom align 2019-10-19 17:53:10 -05:00
John Colvin 52f62c19a4 FillsVM state doesn't need to be observable (every usage is equiv to Observable.Return) 2019-10-19 17:32:14 -05:00
John Colvin 860bda388f ensure latest column settings retrieved from GridSettingsManager 2019-10-19 17:32:14 -05:00
John Colvin 0b73e2744e FillsVM invalidates rows when settings changes (refreshes view) 2019-10-19 17:32:14 -05:00
John Colvin e5d23a987e hide column background color in Fills settings dialog (row bg indicates side) 2019-10-19 17:32:14 -05:00
John Colvin 6ac3eeca2e make fills grid settings pretty 2019-10-19 17:32:14 -05:00
John Colvin 3b73ba25d2 add DefaultConverter prop to ValueMap 2019-10-19 17:32:14 -05:00
John Colvin 34eed73af5 GlobalAlertsView - get rid of sound preview button (play when dropdown closes instead) 2019-10-19 17:32:14 -05:00
John Colvin 2f1f1c007c SP-247 Order Entry Sounds 2019-10-19 17:32:13 -05:00
John Colvin cac4f5da37 OrderBook actually uses default settings by default 2019-10-19 17:32:13 -05:00
John Colvin 58dab25b89 OrderTypeItem template uses Rectangle instead of Badge (the latter has some unresolved rounding issues) 2019-10-19 17:32:13 -05:00
John Colvin 7d100da9b1 GlyphContext - drop unneccesary namespace prefixes 2019-10-19 17:32:13 -05:00
John Colvin c389a47a4a OrderTypeColumn uses a little padding 2019-10-19 17:32:13 -05:00
John Colvin d3d069cf17 fix typo 'ImmutableFontWeight' -> 'ImmutableFontColor' 2019-10-19 17:32:13 -05:00
John Colvin 9576122aeb hook up grid background setting in orderbook 2019-10-19 17:32:13 -05:00
John Colvin a32b40bef6 DIYGrid CellRender.draw_text with layout rounding 2019-10-19 17:32:13 -05:00
John Colvin d9f459ccb5 add TitleCaseConverter/Template to common xaml resources 2019-10-19 17:32:13 -05:00
John Colvin 18bdeea765 add SystemColors palette to color picker gui 2019-10-19 17:32:13 -05:00
John Colvin 87bf9a37cc make GridSettinngs pretty again (starting with OrderBook) 2019-10-19 17:32:13 -05:00
John Colvin 96b6c0fc1d add TypeConverterWrapper 2019-10-19 17:32:12 -05:00
John Colvin 0cee53e287 ReadOnlySelectorWrapper for IList 2019-10-19 17:29:24 -05:00
John Colvin db135a36d9 add 'SelectItemOnDrop' prop to DragDropListBox 2019-10-19 17:29:24 -05:00
John Colvin bd8dbe0071 add general purpose ParameterVMs (immediate use in new GridSettings design) 2019-10-19 17:29:24 -05:00
John Colvin 4f0a01175e OrderBook settings migration rule to enable background color 2019-10-19 17:29:24 -05:00
John Colvin 1f8af68b60 IOrderBookGridSettingsMigrationRule impl in UserSettingsHub 2019-10-19 17:29:24 -05:00
John Colvin 5f2a2a7a56 remove default_vm from ShellSettingsDialogVM (vestige of an earlier time) 2019-10-19 17:29:24 -05:00
John Colvin 6a0e00222c move SystemFontFamilies/Weights lists from FontPicker -> TextUtils 2019-10-19 17:29:24 -05:00
John Colvin c087999c4b add general purpose DIYGridColumn that can be defined directly in xaml 2019-10-19 17:29:24 -05:00
John Colvin 763f8f006d Background TemplateBinding for ExpanderToggleControl 2019-10-19 17:29:24 -05:00
John Colvin 32257f257e strongly type AddRange extensions for IList<T> 2019-10-19 17:29:24 -05:00
John Colvin 4903545642 GridSettings default_font_color shouldn't be transparent 2019-10-19 17:29:24 -05:00
John Colvin 6eb3cf5cf6 better look add button in global alerts dialog 2019-10-19 17:29:24 -05:00
John Colvin cdcd0d0436 get rid of dialog skirt border in global alerts dialog 2019-10-19 17:29:24 -05:00
John Colvin e04ac0592b add LargeMenuItem/Button styles (prereq for new color picker) 2019-10-19 17:29:24 -05:00
John Colvin 0a3cbc8539 AddBox icon 2019-10-19 17:29:24 -05:00
John Colvin fb2385041c add VisualState template/proxy dependency props to support visual state shared style resources 2019-10-19 17:29:24 -05:00
John Colvin 98685e59b3 MouseStateDefinition special impl for Button/MenuItem 'IsPressed' props 2019-10-19 17:29:24 -05:00
John Colvin bf9e6a645a add TabularColorPicker/ColorPickerSuite (more user friendly color gui) 2019-10-19 17:29:24 -05:00
John Colvin 6d02d9a508 ColorPickerControl SelectedColorChanged event 2019-10-19 17:29:23 -05:00
John Colvin 2a8a459cdd ColorPickerControl should not automatically bind its DataContext 2019-10-19 17:29:23 -05:00
John Colvin e25d039f3b add ButtonStyle prop to PopupControl 2019-10-19 17:29:23 -05:00
John Colvin 5cdaa60b86 assert_style_type shouldn't require exact type match, just needs to be assignable 2019-10-19 17:29:23 -05:00
John Colvin ff33d0d735 move ReverseComparer to spark_core 2019-10-19 17:29:23 -05:00
John Colvin 8ba1c7a602 refactor ComparerBuilder.add -> Add to support object initializer syntax 2019-10-19 17:29:23 -05:00
John Colvin 8ef5dd56ee remove unused FontFamily enum 2019-10-19 17:29:23 -05:00
John Colvin c3c1a33081 AlgoStudio - remove algo name from buttons, dock save/revert on bottom 2019-10-19 17:29:23 -05:00
John Colvin 4c0461311f AlgoStudio - hide vertical scrollbar 2019-10-19 17:29:23 -05:00
116 changed files with 5998 additions and 2622 deletions

No files matched your search

+2
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),
@@ -125,6 +126,7 @@ namespace mbts.ui {
public static ImageSource Accounting { get { return load_resource_and_freeze("Accounting"); } }
public static ImageSource Add { get { return load_resource_and_freeze("Add"); } }
public static ImageSource AddBox => load_resource_and_freeze(AddBoxKey);
public static ImageSource AddBoxes => create_icon(AddBoxesGeometryKey);
public static ImageSource AddEncircled { get { return load_resource_and_freeze("AddEncircled"); } }
public static ImageSource AddFile => create_icon(Smashicons.AddFileGeometryKey);
+41
View File
@@ -13,6 +13,47 @@
M19.522852,25.255011L20.920847,25.256994 20.920847,29.729012 19.522852,29.729012z M22.826874,21.952002L25.989905,25.113989 25.001887,26.101996 21.838856,22.941015z M25.269894,19.506992L29.742937,19.509007 29.742937,20.906012 25.269894,20.907995z M10.519729,14.945011C11.300744,14.954991,12.079745,15.103001,12.818766,15.386997L10.878742,17.327C9.6457264,17.200016,8.368704,17.592014,7.4257004,18.532993L3.597655,22.359015C1.9326275,24.025 1.9316508,26.734991 3.597655,28.40299 5.2636887,30.068001 7.9747178,30.066017 9.6397144,28.40299L13.468767,24.575018C14.410794,23.633001,14.800783,22.355993,14.674774,21.122015L16.615807,19.181004C17.523806,21.547003,17.034817,24.325995,15.126776,26.233008L11.298761,30.061012C8.713709,32.644997 4.5236599,32.644997 1.9396465,30.061012 -0.64638097,27.475012 -0.64638097,23.286017 1.9386395,20.702002L5.7666852,16.875004C7.0777046,15.562992,8.8017227,14.922001,10.519729,14.945011z M2.1536381,10.959994L6.6257035,10.959994 6.6266801,12.359988 2.1536381,12.359988z M6.7686803,5.894014L9.9307338,9.0549947 8.9427158,10.044008 5.7806932,6.8819901z M10.976735,2.1390082L12.374761,2.1390082 12.375768,6.6130081 10.975728,6.6130081z M25.382902,0C27.076922,-1.8170738E-08 28.770941,0.6470033 30.063955,1.9389956 32.646992,4.5220043 32.646992,8.7130147 30.061941,11.297L26.234902,15.126011C24.326891,17.032993,21.546862,17.521,19.181845,16.614994L21.121868,14.674015C22.35586,14.798008,23.63389,14.407992,24.575886,13.467013L28.403933,9.6390096C30.067953,7.972994 30.069967,5.2619945 28.403933,3.5960094 26.738905,1.9310002 24.0269,1.9320073 22.361872,3.5960094L18.533827,7.4249893C17.591829,8.3659993,17.201811,9.6440145,17.32782,10.87601L15.385781,12.817996C14.477781,10.453005,14.968784,7.6730062,16.875818,5.7659928L20.702858,1.9379885C21.994864,0.64599622,23.688883,-1.8170738E-08,25.382902,0z
</sys:String>
<DrawingImage x:Key="{x:Static mbts:Icons.AddBoxKey}">
<DrawingImage.Drawing>
<DrawingGroup>
<GeometryDrawing Brush="#E0E0E0">
<GeometryDrawing.Geometry>
<PathGeometry>
<PathGeometry.Figures>
<PathFigure StartPoint="0,0" IsClosed="True" IsFilled="True">
<PolyLineSegment>
<PolyLineSegment.Points>
<Point>100,0</Point>
<Point>100,100</Point>
<Point>0,100</Point>
</PolyLineSegment.Points>
</PolyLineSegment>
</PathFigure>
</PathGeometry.Figures>
</PathGeometry>
</GeometryDrawing.Geometry>
</GeometryDrawing>
<GeometryDrawing Brush="Black">
<GeometryDrawing.Pen>
<Pen Brush="Black" Thickness="4"/>
</GeometryDrawing.Pen>
<GeometryDrawing.Geometry>
<PathGeometry>
<PathGeometry.Figures>
<PathFigure StartPoint="25,50">
<LineSegment Point="75,50"/>
</PathFigure>
<PathFigure StartPoint="50,25">
<LineSegment Point="50,75"/>
</PathFigure>
</PathGeometry.Figures>
</PathGeometry>
</GeometryDrawing.Geometry>
</GeometryDrawing>
</DrawingGroup>
</DrawingImage.Drawing>
</DrawingImage>
<!--AddBoxes (the icon is free to use if we include this information in our credit section)
Source: https://www.flaticon.com/authors/gregor-cresnar
Ttile: Add free icon
@@ -116,7 +116,7 @@ namespace mbts.spark {
{ Columns.WorkingAsks.ToString(), GridColumnSettingsFeatureFlags.ImmutableFontColor },
{ Columns.WorkingBids.ToString(), GridColumnSettingsFeatureFlags.ImmutableFontColor }
};
grid_settings_manager = new SimpleGridSettingsManager<Row>(columns, col_settings_flags, row_vms);
grid_settings_manager = new GridSettingsManager(columns, col_settings_flags);
// this will initialize the immutable properties that correspond to the col_settings_flags above (e.g. Bid/Ask font_color_override, etc.)
grid_settings_manager.load_from(settings_manager.current_settings());
//
@@ -159,14 +159,18 @@ namespace mbts.spark {
ask_price_filter, ask_merged_size_filter, ask_direct_size_filter, ask_implied_size_filter,
last_price_filter, last_price_from_spread_filter, last_size_filter, day_change_filter, settlement_filter,
session_low_filter, session_high_filter, position_filter, raw_day_position_filter, working_bid_volume_filter, working_ask_volume_filter, volume_filter, security_key_filter),
settings_manager.subscribe_current().OnDispatcher().Subscribe(grid_settings_manager),
settings_manager.subscribe_current().OnDispatcher().Subscribe(s => {
grid_settings_manager.load_from(s);
foreach (var r in row_vms)
r.raise_invalidated();
}),
WorkLoop.Start("MarketView", _refresh)
);
}
public ObservableCollection<ColumnBase> columns { get; } = new ObservableCollection<ColumnBase>();
public SimpleGridSettingsManager<Row> grid_settings_manager { get; }
public GridSettingsManager grid_settings_manager { get; }
public GridSettings get_grid_save_state() {
if (_settings_manager.instance_settings == null)
@@ -7,7 +7,6 @@ using bts.utils;
using mbts.persist;
using mbts.ui;
using mbts.ui.settings;
using mbts.ui.shell;
using mbts.ui.spark;
using mbts.ui.utility;
using mbts.utils;
@@ -79,7 +78,7 @@ namespace mbts.spark {
if (match is ui.controls.DIYGrid.IKeyedColumn<MarketViewView.Columns> kcol) {
var col_comp = MarketViewVM.get_column_comparer(kcol.key);
if (col_comp != null) {
comp_builder.add(sc.is_ascending ? col_comp : col_comp.reverse());
comp_builder.Add(sc.is_ascending ? col_comp : col_comp.reverse());
} else {
Log.Ui.warn($"{_window_type} is unable to find the row comparer for the '{sc.col_name}' column.");
}
@@ -146,16 +145,17 @@ namespace mbts.spark {
SparkCommands.change_securities_command(() => (update_securities_cmd, _state.security_criteria)),
// wrap the grid settings view factory in a dialogCommand here since the vm is destroyed/recreated when the security criteria changes
SparkCommands.window_settings_command(new DialogCommand(() => {
var vm = new ShellSettingsDialogVM<GridSettings, SimpleGridSettingsVM>(
var vm = new ShellSettingsDialogVM<GridSettings, GridSettingsVM>(
_settings_manager,
us => us.market_view_grid,
UserSettings.FileExtensions.GRID,
s => _vm.grid_settings_manager.create_vm(
s => {
var gs_vm = _vm.grid_settings_manager.create_vm();
// we need to get the most up to date GridSettings when launching b/c users may
// have changed some settings on the grid directly, like column widths.
s.with(_vm.columns.Select(c => c.create()))
)
);
gs_vm.load(s.with(_vm.columns.Select(c => c.create())));
return gs_vm;
});
vm.SubscribeDisposed(literal_title_source.OnDispatcher().Subscribe(t => vm.title = t));
var dialog = new ShellSettingsDialog { DataContext = vm };
vm.RequestClose += (s, e) => WindowUtilities.force_close(dialog);
@@ -25,7 +25,6 @@ namespace mbts.spark {
readonly TimeAndSalesModel _model;
IWindowSettingsManager<GridSettings> _settings_manager;
public TimeAndSalesVM(IBus bus, IWindowSettingsManager<GridSettings> settings_manager, TimeAndSalesModel model, StartupData startup_data)
: base(RXCollection.CreateBuffer(model.trades, 1000), Enumerable.Empty<SortingCriteria>()) {
_bus = bus;
@@ -52,23 +51,26 @@ namespace mbts.spark {
columns.Add(new RenderTextColumn<Row, TimeAndSalesView.Columns>(col_key, r => r[col_key], filter, filter_template, r => _get_aggressor_brush(r))
{ id = col_id(col_key), Header = header, Width = width, sort_member_path = sort_member_path, horizontal_alignment = h_alignment }
);
// be sure to load GridSettings AFTER the columns collection has been populated, the grid settings manager looks columns by id to apply the appropriate settings,
// if the collection is not populated, the column settings will not be applied
grid_settings_manager = new SimpleGridSettingsManager<Row>(columns, new Dictionary<string, GridColumnSettingsFeatureFlags> { }, row_vms);
grid_settings_manager = new GridSettingsManager(columns);
grid_settings_manager.load_from(settings_manager.current_settings());
_resources.AddRange(
show_ladder,
DataGridFilterHelper.hook_filters(
rows_view, time_filter, security_filter, price_filter, size_filter, aggressor_filter, security_key_filter),
settings_manager.subscribe_current().OnDispatcher().Subscribe(grid_settings_manager)
);
settings_manager.subscribe_current().OnDispatcher().Subscribe(s => {
grid_settings_manager.load_from(s);
foreach (var r in row_vms)
r.raise_invalidated();
}));
rows_view.set_sort_descriptions(startup_data.sorting_criterion, columns);
}
public ObservableCollection<DIYGrid.ColumnBase> columns { get; } = new ObservableCollection<DIYGrid.ColumnBase>();
public DelegateCommandParamObserver<Row> show_ladder { get; }
public SimpleGridSettingsManager<Row> grid_settings_manager { get; }
public GridSettingsManager grid_settings_manager { get; }
public GridSettings get_grid_save_state() {
if (_settings_manager.instance_settings == null)
@@ -7,7 +7,6 @@ using mbts.co2;
using mbts.persist;
using mbts.ui;
using mbts.ui.settings;
using mbts.ui.shell;
using mbts.ui.spark;
using mbts.ui.utility;
using mbts.utils;
@@ -70,16 +69,17 @@ namespace mbts.spark {
SparkCommands.change_securities_command(() => (update_securities_cmd, _state.security_criteria)),
// wrap the grid settings view factory in a dialogCommand here since the vm is destroyed/recreated when the security criteria changes
SparkCommands.window_settings_command(new DialogCommand(() => {
var vm = new ShellSettingsDialogVM<GridSettings, SimpleGridSettingsVM>(
_settings_manager,
s => s.time_and_sales_grid,
UserSettings.FileExtensions.GRID,
s => _vm.value.grid_settings_manager.create_vm(
// we need to get the most up to date GridSettings when launching b/c users may
// have changed some settings on the grid directly, like column widths.
s.with(_vm.value.columns.Select(c => c.create()))
)
);
var vm = new ShellSettingsDialogVM<GridSettings, GridSettingsVM>(
_settings_manager,
s => s.time_and_sales_grid,
UserSettings.FileExtensions.GRID,
s => {
var gs_vm = _vm.value.grid_settings_manager.create_vm();
// we need to get the most up to date GridSettings when launching b/c users may
// have changed some settings on the grid directly, like column widths.
gs_vm.load(s.with(_vm.value.columns.Select(c => c.create())));
return gs_vm;
});
vm.SubscribeDisposed(literal_title_source.OnDispatcher().Subscribe(t => vm.title = t));
var dialog = new ShellSettingsDialog { DataContext = vm };
vm.RequestClose += (s, e) => WindowUtilities.force_close(dialog);
@@ -153,8 +153,13 @@
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<ListBox
x:Name="_tree_view"
Grid.RowSpan="2"
ItemsSource="{Binding definitions_view}"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
@@ -173,7 +178,7 @@
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<ScrollViewer Grid.Column="1" Grid.IsSharedSizeScope="True" Margin="8,4">
<ScrollViewer Grid.Column="1" Grid.IsSharedSizeScope="True" VerticalScrollBarVisibility="Auto" Margin="8,4,8,0">
<Grid x:Name="edit_pane">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
@@ -429,19 +434,25 @@
Margin="0,6,0,0"
Padding="12,8"
/>
<StackPanel Grid.Row="3" HorizontalAlignment="Right" Orientation="Horizontal">
<StackPanel.Margin>
<Thickness Left="{StaticResource DialogSkirtPadding.Left}" Top="{StaticResource VerticalPush2}" Right="{StaticResource DialogSkirtButtonPadding.Right}" Bottom="{StaticResource DialogSkirtPadding.Bottom}"/>
</StackPanel.Margin>
<Button ToolTipService.ShowOnDisabled="True" Margin="{StaticResource DialogSkirtButtonMargin}" Command="{Binding save_cmd}" CommandParameter="{Binding selected_definition}" Width="100" ToolTip="{Binding save_cmd_tooltip}" IsDefault="True">
<TextBlock Text="{Binding selected_definition.name, StringFormat='{}Save {0}'}"/>
</Button>
<Button ToolTipService.ShowOnDisabled="True" Margin="{StaticResource DialogSkirtButtonMargin}" Command="{Binding revert_cmd}" CommandParameter="{Binding selected_definition}" Width="100" ToolTip="{Binding revert_cmd_tooltip}">
<TextBlock Text="{Binding selected_definition.name, StringFormat='{}Revert {0}'}"/>
</Button>
</StackPanel>
</Grid>
</ScrollViewer>
<c:DialogSkirt
Grid.Row="1"
Grid.Column="1"
Background="Transparent"
BorderThickness="0"
>
<c:DialogSkirt.RightItems>
<x:Array Type="{x:Type Button}">
<Button ToolTipService.ShowOnDisabled="True" Margin="{StaticResource DialogSkirtButtonMargin}" Command="{Binding save_cmd}" CommandParameter="{Binding selected_definition}" Width="100" ToolTip="{Binding save_cmd_tooltip}" IsDefault="True">
Save Algo
</Button>
<Button ToolTipService.ShowOnDisabled="True" Margin="{StaticResource DialogSkirtButtonMargin}" Command="{Binding revert_cmd}" CommandParameter="{Binding selected_definition}" Width="100" ToolTip="{Binding revert_cmd_tooltip}">
Revert Algo
</Button>
</x:Array>
</c:DialogSkirt.RightItems>
</c:DialogSkirt>
</Grid>
</AdornerDecorator>
</UserControl>
@@ -9,6 +9,7 @@ using System.Collections.Generic;
using System.Linq;
using System.Reactive.Disposables;
using System.Reactive.Linq;
using System.Threading.Tasks;
using System.Windows;
using StartupData = bts.shell.AlgoStudioWindowFactory.StartupData;
@@ -68,23 +69,12 @@ namespace mbts.spark {
.Subscribe(sec_map => _view.Resources[ResourceKeys.Securities] = sec_map));
// add definition
_commands.Add(new SparkCommandCommand("Add Algo", Icons.AddBoxes, _vm.add_definition_cmd));
// clone definition
var clone_selected_definition_spark_cmd = new SparkCommandCommand(string.Empty, Icons.DocumentCopy, _vm.clone_selected_definition_cmd);
_commands.Add(clone_selected_definition_spark_cmd);
_resources.Add(_vm.subscribe_definition().OnDispatcher().Subscribe(
def => clone_selected_definition_spark_cmd.title = def == null ? $"Clone (None Selected)" : $"Clone '{def.name}'"));
// save
if (_ui_configuration.can_save_specific) {
var content_is_dirty_src = _vm.subscribe_definition().SelectManyInSeries(def => def?.subscribe_is_definition_dirty() ?? Observable.Return(false));
_commands.Add(new ReactiveSparkCommand(_vm.subscribe_definition().OnDispatcher().Select(c => $"Save '{c?.name}'"), Icons.DiskFile, () => {
if (_vm.selected_definition is AlgoDefinitionVM d)
_vm.save_definition(d).ignore();
}, content_is_dirty_src));
}
// save all
var save_all_cmd = new ObservantAsyncCommand(_vm.save_all);
_commands.Add(new SparkCommandCommand("Save All", Icons.Disk, save_all_cmd));
_resources.Add(_vm.subscribe_dirty().OnDispatcher().Subscribe(save_all_cmd));
_commands.Add(new SparkCommandCommand("Clone Algo", Icons.DocumentCopy, _vm.clone_selected_definition_cmd));
_commands.Add(new SparkCommandCommand("Save Algo", Icons.DiskFile, _vm.save_cmd) {
show_in_toolbar = _ui_configuration.can_save_specific
});
_commands.Add(new SparkCommandCommand("Save All", Icons.Disk, ObservantAsyncCommand.create(_vm.save_all, out var save_all_observer)));
_resources.Add(_vm.subscribe_dirty().OnDispatcher().Subscribe(save_all_observer));
// undo/redo
if (_ui_configuration.can_undo_redo) {
// undo
@@ -117,30 +107,11 @@ namespace mbts.spark {
_resources.Add(can_redo_source.Subscribe(redo_cmd));
}
// remove
var remove_spark_cmd = new ObservantSparkCommandCommand<IAlgoStudioContentVM>(Icons.Trash, v => $"Remove '{v?.name}'", v => v != null, value => {
if (value is AlgoDefinitionVM d) {
string prompt; MessageBoxImage icon;
if (d.key.mode == AlgoKey.Mode.Persisted) {
prompt = $"Are you sure you want to delete '{d.name}?'";
icon = MessageBoxImage.Question;
} else {
// algo hasn't even been created yet (just an unsaved placeholder item), no need to get user confirmation in this case.
prompt = null;
icon = default;
}
if (prompt != null) {
var resp = BTSMessageBox.Show(prompt, "Algo Studio", BTSMessageBoxButtons.Yes | BTSMessageBoxButtons.No, icon);
switch (resp) {
case BTSMessageBoxButtons.Yes: break;
case BTSMessageBoxButtons.No:
default: return;
}
}
_vm.remove_definition(d).ignore();
}
});
_commands.Add(remove_spark_cmd);
_resources.Add(_vm.subscribe_definition().OnDispatcher().Subscribe(remove_spark_cmd));
_commands.Add(new SparkCommandCommand("Remove Algo", Icons.Trash, ObservantAsyncCommand.create<AlgoDefinitionVM>(
vm => BTSMessageBox.Confirm($"Are you sure you want to delete '{vm.name}?'", Title) ? _vm.remove_definition(vm) : Task.CompletedTask,
vm => vm != null,
out var remove_cmd_observer)));
_resources.Add(_vm.subscribe_definition().OnDispatcher().Subscribe(remove_cmd_observer));
}
void _dispose_core() {
@@ -149,4 +120,4 @@ namespace mbts.spark {
Disposable.dispose(ref _vm);
}
}
}
}
@@ -38,10 +38,10 @@ namespace mbts.spark {
IWindowSettingsManager<GridSettings> _settings_manager;
int _total_row_count;
public FillsVM(IBus bus, OrdersModel model, IObservable<FillsSettings> state_obs, IWindowSettingsManager<GridSettings> settings_manager)
: this(bus, model, state_obs, _get_observable(bus, model, state_obs), settings_manager) { }
public FillsVM(IBus bus, OrdersModel model, FillsSettings settings, IWindowSettingsManager<GridSettings> settings_manager)
: this(bus, model, settings, _get_observable(bus, model, settings), settings_manager) { }
public FillsVM(IBus bus, OrdersModel model, IObservable<FillsSettings> state_obs, IObservable<TableUpdate<FillTuple, string>> fills_source, IWindowSettingsManager<GridSettings> settings_manager) {
public FillsVM(IBus bus, OrdersModel model, FillsSettings settings, IObservable<TableUpdate<FillTuple, string>> fills_source, IWindowSettingsManager<GridSettings> settings_manager) {
_model = model;
_settings_manager = settings_manager;
/// Columns
@@ -132,7 +132,10 @@ namespace mbts.spark {
// rows
_rows_manager = DIYGridRowsManager.create(FillsWindow.window_type_name, Columns.FillKey, col_tups, col_tups.WrapSelector(t => (t.col, t.key)), _get_column_comparer, fs => fs.build_predicate());
// grid settings
grid_settings_manager = new SimpleGridSettingsManager<Row>(columns, new Dictionary<string, GridColumnSettingsFeatureFlags> { }, rows);
var col_flags = EnumHelper.get_values<Columns>().ToDictionary(
c => c.ToString(),
c => GridColumnSettingsFeatureFlags.ImmutableBackgroundColor);
grid_settings_manager = new GridSettingsManager(columns, col_flags);
// this will initialize the immutable properties that correspond to the col_settings_flags above (e.g. Bid/Ask font_color_override, etc.)
if (_settings_manager != null)
grid_settings_manager.load_from(_settings_manager.current_settings());
@@ -145,6 +148,19 @@ namespace mbts.spark {
_unfiltered_fills_changed += o.OnNext;
return Disposable.Create(() => _unfiltered_fills_changed -= o.OnNext);
});
if (settings.grid != null && _settings_manager != null) {
// this means we are not using defaults,
// Note: settings_manager in turn notifies grid_settings_manager of this update
_settings_manager.instance_settings = settings.grid;
}
_rows_manager.on_settings(settings.filters ?? new FillsFilterStates {
fill_time = new TimeFilterTuple(
TimeFilterMode.CMEOpen,
TimeTextTuple.FromDateTime(DateTime.Today),
TimeTextTuple.FromDateTime(DateTime.Today.AddDays(1)))
},
settings.sorting_criterion);
// resources/subscriptions
_dispose = new CompositeDisposable(
ObservableAutoWrapper.connect(col_tups, columns, ct => ct.col),
ObservableAutoWrapper.connect_item_hooks(col_tups, ct => ct.filter as IDisposable),
@@ -154,22 +170,12 @@ namespace mbts.spark {
Set(ref _total_row_count, t.state.Count, nameof(total_row_count));
_unfiltered_fills_changed?.Invoke(t);
}),
state_obs.OnDispatcher().Subscribe(s => {
if (s.grid != null && _settings_manager != null) {
// this means we are not using defaults,
// Note: settings_manager in turn notifies grid_settings_manager of this update
_settings_manager.instance_settings = s.grid;
}
var fs = s.filters ?? new FillsFilterStates {
fill_time = new TimeFilterTuple(
TimeFilterMode.CMEOpen,
TimeTextTuple.FromDateTime(DateTime.Today),
TimeTextTuple.FromDateTime(DateTime.Today.AddDays(1)))
};
_rows_manager.on_settings(fs, s.sorting_criterion);
}),
model.subscribe_all_algos().SelectKeyValue(AlgoDataVM.from_data, d => AlgoID.from_algo_key(d.data.key)).OnDispatcher().Subscribe(root_algo_type_observer),
settings_manager == null ? Disposable.empty : settings_manager.subscribe_current().OnDispatcher().Subscribe(grid_settings_manager)
settings_manager == null ? Disposable.empty : settings_manager.subscribe_current().OnDispatcher().Subscribe(s => {
grid_settings_manager.load_from(s);
foreach (var r in rows)
r.raise_invalidated();
})
);
// local methods
void add_col<TFilter>(Columns col_key, string header, Func<Row, string> get_text, IDataGridColumnPersistentFilter<FillTuple, TFilter> filter, string filter_template, double width) =>
@@ -192,7 +198,7 @@ namespace mbts.spark {
public ObservableCollection<Row> rows { get; } = new ObservableCollection<Row>();
public int total_row_count => _total_row_count;
public SimpleGridSettingsManager<Row> grid_settings_manager { get; }
public GridSettingsManager grid_settings_manager { get; }
public GridSettings get_grid_save_state() {
if (_settings_manager.instance_settings == null)
@@ -282,6 +288,8 @@ namespace mbts.spark {
account = fill_tuple.account;
}
public event Action invalidated;
public DateTime fill_time => _fill.Timestamp.to_local_time();
public TimeInForce time_in_force => _order.time_in_force();
public OrderType order_type => _order.order_type();
@@ -310,8 +318,8 @@ namespace mbts.spark {
public override bool Equals(object obj) => Equals(obj as Row);
public override int GetHashCode() => _fill.FillKey.GetHashCode() ^ _order.Key.GetHashCode();
public event Action invalidated;
void IRowVM.raise_invalidated() => invalidated?.Invoke();
public void raise_invalidated() =>
invalidated?.Invoke();
}
}
}
}
@@ -26,7 +26,6 @@ namespace mbts.spark {
FillsView _view;
FillsSettings _state;
IReadOnlyList<SparkCommand> _commands;
IDisposable _subscriptions;
public FillsWindow(FillsWindowFactory factory, IBus bus, OrdersModel orders_model, FillsSettings state, WindowSettingsManager<GridSettings> settings_manager)
: base("Fills", bus, factory, SparkCommands.ShowFills().icon) {
@@ -36,8 +35,6 @@ namespace mbts.spark {
thread_id = config.Configuration.get<bts.config.MultiThreadedWindows>().fills ? "Fills" : null;
}
event Action<FillsSettings> _state_changed;
public override string window_type => window_type_name;
public override UIElement Content => _view;
public override IEnumerable<SparkCommand> Commands => _commands;
@@ -54,38 +51,29 @@ namespace mbts.spark {
public void thread_init() {
_init_guard.check_access();
Log.notnull(_state);
var state_src = Observable.FromEvent<Action<FillsSettings>, FillsSettings>(cb => _state_changed += cb, cb => _state_changed -= cb).StartWith(_state);
_vm = new FillsVM(_bus, _orders_model, state_src, _settings_manager);
_vm = new FillsVM(_bus, _orders_model, _state, _settings_manager);
_view = new FillsView { DataContext = _vm };
_commands = new List<SparkCommand> {
// wrap the grid settings view factory in a dialogCommand here since the vm is destroyed/recreated when the security criteria changes
SparkCommands.window_settings_command(new DialogCommand(() => {
var vm = new ShellSettingsDialogVM<GridSettings, SimpleGridSettingsVM>(
var vm = new ShellSettingsDialogVM<GridSettings, GridSettingsVM>(
_settings_manager,
us => us.fills_grid,
UserSettings.FileExtensions.GRID,
s => _vm.grid_settings_manager.create_vm(
s => {
var gs_vm = _vm.grid_settings_manager.create_vm();
// we need to get the most up to date GridSettings when launching b/c users may
// have changed some settings on the grid directly, like column widths.
s.with(_vm.columns.Select(c => c.create()))
)
);
gs_vm.load(s.with(_vm.columns.Select(c => c.create())));
return gs_vm;
});
vm.SubscribeDisposed(literal_title_source.OnDispatcher().Subscribe(t => vm.title = t));
var dialog = new ShellSettingsDialog { DataContext = vm };
vm.RequestClose += (s, e) => WindowUtilities.force_close(dialog);
return dialog;
}))
};
_subscriptions = Disposable.Create(() =>
_state_changed?.Invoke(
_state = new FillsSettings(_state.orders,
_vm.get_sorting_criterion().ToList(),
_vm.get_filters(),
_vm.grid_settings_manager.create()
)
)
);
}
public override void Dispose() {
@@ -29,6 +29,7 @@ using static mbts.ui.controls.DIYGrid;
using OrderType = bts.api.orderman.CustomOrderDefinition.CustomOneofCase;
using System.Windows.Input;
using System.Reactive.Disposables;
using mbts.ui.dpi;
namespace mbts.spark {
// This interface is used to hide ColumnBase.SortDirection, and offer a SortDirection member that does NOT require a DependencyProperty lookup.
@@ -46,10 +47,10 @@ namespace mbts.spark {
public OrderBookSortingData(IEnumerable<ICVOrderBookColumn> column_sorting_hierarchy) {
var comp_builder = new ComparerBuilder<OrderBookRowVM>();
foreach (var col in column_sorting_hierarchy)
comp_builder.add(col.SortDirection.Value == ListSortDirection.Ascending ? col.comparer : col.comparer.reverse());
comp_builder.Add(col.SortDirection.Value == ListSortDirection.Ascending ? col.comparer : col.comparer.reverse());
if (!column_sorting_hierarchy.Any(col => col.key == Columns.OrderKey)) {
// the resultant comparer must to be 100% deterministic, as SortedList (the building block of the SortedTree) uses a BinarySearch to find/remove items and does NOT check for item-equality.
comp_builder.add(Comparer<OrderBookRowVM>.Create((l, r) => l.order_key.CompareTo(r.order_key)));
comp_builder.Add(Comparer<OrderBookRowVM>.Create((l, r) => l.order_key.CompareTo(r.order_key)));
}
comparer = comp_builder.to_comparer();
sorting_criterion = column_sorting_hierarchy.Select(col => new SortingCriteria(col.id, col.SortDirection.GetValueOrDefault() == ListSortDirection.Ascending)).ToArray();
@@ -582,7 +583,7 @@ namespace mbts.spark {
public void draw(OrderBookRowVM row, DrawingContext dc, ColumnGeometry position, double row_height) =>
CellRender.draw_text(dc, _get_glyph_context(row), font_size, _get_text(row), position, row_height,
RenderUtils.get_foreground(row, font_brush), null, horizontal_alignment, VerticalAlignment.Center, DefaultCellPadding);
RenderUtils.get_foreground(row, font_brush), background_brush, horizontal_alignment, VerticalAlignment.Center, DefaultCellPadding);
}
#region Explicit Column Defs
@@ -592,7 +593,7 @@ namespace mbts.spark {
public void draw(OrderBookRowVM row, DrawingContext dc, ColumnGeometry position, double row_height) =>
CellRender.draw_text(dc, _get_glyph_context(row), font_size, row.side.ToString(), position, row_height,
RenderUtils.get_side_brush(row), null, horizontal_alignment, VerticalAlignment.Center, DefaultCellPadding);
RenderUtils.get_side_brush(row), background_brush, horizontal_alignment, VerticalAlignment.Center, DefaultCellPadding);
}
public class SizeColumn : OrderbookColumnBase<UIntMinMaxState>, IRenderColumn<OrderBookRowVM> {
@@ -601,7 +602,7 @@ namespace mbts.spark {
public void draw(OrderBookRowVM row, DrawingContext dc, ColumnGeometry position, double row_height) =>
CellRender.draw_text(dc, _get_glyph_context(row), font_size, row.quantity.ToString(), position, row_height,
RenderUtils.get_side_brush(row), null, horizontal_alignment, VerticalAlignment.Center, DefaultCellPadding);
RenderUtils.get_side_brush(row), background_brush, horizontal_alignment, VerticalAlignment.Center, DefaultCellPadding);
}
public class FilledColumn : OrderbookColumnBase<UIntMinMaxState>, IRenderColumn<OrderBookRowVM> {
@@ -610,7 +611,7 @@ namespace mbts.spark {
public void draw(OrderBookRowVM row, DrawingContext dc, ColumnGeometry position, double row_height) =>
CellRender.draw_text(dc, _get_glyph_context(row), font_size, row.filled_quantity.ToString(), position, row_height,
RenderUtils.get_side_brush(row), null, horizontal_alignment, VerticalAlignment.Center, DefaultCellPadding);
RenderUtils.get_side_brush(row), background_brush, horizontal_alignment, VerticalAlignment.Center, DefaultCellPadding);
}
public class OrderBookOrderTypeColumn : OrderTypeColumn<Columns>, ICVOrderBookColumn {
@@ -676,7 +677,7 @@ namespace mbts.spark {
public void draw(OrderBookRowVM row, DrawingContext dc, ColumnGeometry position, double row_height) =>
CellRender.draw_text(dc, _get_glyph_context(row), font_size, row.piq_text, position, row_height,
RenderUtils.get_foreground(row, font_brush), null, HorizontalAlignment.Right, VerticalAlignment.Center, DefaultCellPadding);
RenderUtils.get_foreground(row, font_brush), background_brush, HorizontalAlignment.Right, VerticalAlignment.Center, DefaultCellPadding);
}
public class SecurityColumn : FrameworkElementColumnBase<Columns>, ICVOrderBookColumn, IFilterableColumn {
@@ -720,14 +721,23 @@ namespace mbts.spark {
security_view.SetBinding(TextBlock.FontFamilyProperty, new Binding(nameof(font_family)) { Source = this });
security_view.SetBinding(TextBlock.FontWeightProperty, _font_weight_binding);
//
var grid = new Grid { VerticalAlignment = VerticalAlignment.Center };
Grid.SetColumn(security_view, 1);
var grid = new Grid {
VerticalAlignment = VerticalAlignment.Center
};
Grid.SetColumn(toggle_button, 1);
Grid.SetColumn(security_view, 2);
grid.ColumnDefinitions.Add(new ColumnDefinition { Width = new GridLength(10.0.RoundLayoutValue(), GridUnitType.Pixel) });
grid.ColumnDefinitions.Add(new ColumnDefinition { Width = GridLength.Auto });
grid.ColumnDefinitions.Add(new ColumnDefinition { Width = new GridLength(1, GridUnitType.Star) });
grid.Children.Add(toggle_button);
grid.Children.Add(security_view);
//
return new Border { Child = grid, Margin = new Thickness(10, 0, 0, 0) };
var container = new Grid {
HorizontalAlignment = HorizontalAlignment.Stretch,
VerticalAlignment = VerticalAlignment.Stretch
};
container.SetBinding(Control.BackgroundProperty, new Binding(nameof(background_brush)) { Source = this });
container.Children.Add(grid);
return container;
}
protected override void OnSortDirectionChanged(ListSortDirection? new_value) {
@@ -141,22 +141,24 @@ namespace mbts.spark {
toggle_show_fills_cmd = new DelegateCommand(() => show_fills = !show_fills);
launch_fills_settings_cmd = concated_disposable(new DialogCommand(() => {
const string title = "Order Book 'Mini' Fills Grid";
var vm = new ShellSettingsDialogVM<GridSettings, SimpleGridSettingsVM>(
var vm = new ShellSettingsDialogVM<GridSettings, GridSettingsVM>(
_fills_settings_manager,
us => us.fills_grid,
UserSettings.FileExtensions.GRID,
s => fills_vm.value.grid_settings_manager.create_vm(
s => {
var gs_vm = fills_vm.value.grid_settings_manager.create_vm();
// we need to get the most up to date GridSettings when launching b/c users may
// have changed some settings on the grid directly, like column widths.
s.with(fills_vm.columns.Select(c => c.create()))
),
gs_vm.load(s.with(fills_vm.columns.Select(c => c.create())));
return gs_vm;
},
show_save_as_defaults: false);
// TODO: bind to main window's Title
var dialog = new ShellSettingsDialog { Title = title, DataContext = vm };
vm.RequestClose += (s, e) => WindowUtilities.force_close(dialog);
return dialog;
}));
grid_settings_manager = new OrderBookGridSettingsManager(bus, notification_service, model, rows, core_columns);
grid_settings_manager = new OrderBookGridSettingsManager(bus, notification_service, rows, core_columns);
grid_settings_manager.algo_tree_display_mode_changed += _on_filter_changed;
IDisposable attach_vm_hooks = null;
begin_attaching += on_begin_attaching;
@@ -245,7 +247,7 @@ namespace mbts.spark {
.Subscribe(is_visible => {
if (is_visible) {
var init = _cached_fills_settings.to_startup_data();
fills_vm.value = new FillsVM(bus, model, Observable.Return(init), model.on_fill_tuples.Filter(mk_fills_slave_predicate_src(), ft => ft.fill.FillKey, _ => false), _fills_settings_manager);
fills_vm.value = new FillsVM(bus, model, init, model.on_fill_tuples.Filter(mk_fills_slave_predicate_src(), ft => ft.fill.FillKey, _ => false), _fills_settings_manager);
} else if (fills_vm.value != null) {
_cached_fills_settings = _get_fills_settings(fills_vm.value);
fills_vm.value = null;
@@ -261,7 +263,8 @@ namespace mbts.spark {
}),
_columns);
if (settings_manager != null) {
concat_disposable(settings_manager.subscribe_current().OnDispatcher().Subscribe(grid_settings_manager));
concat_disposable(
settings_manager.subscribe_current().OnDispatcher().Subscribe(grid_settings_manager.load_from));
}
IObservable<Func<FillTuple, bool>> mk_fills_slave_predicate_src() =>
Observable.Create<Func<FillTuple, bool>>(o => {
@@ -176,8 +176,10 @@ namespace mbts.spark {
return _vm.grid_settings_manager.create_vm(s);
}
);
gs_vm.SubscribeDisposed(literal_title_source.OnDispatcher().Subscribe(t => gs_vm.title = t));
var dialog = new ShellSettingsDialog { DataContext = gs_vm };
gs_vm.SubscribeDisposed(Disposable.Create(
literal_title_source.OnDispatcher().Subscribe(t => gs_vm.title = t),
_bus.subscribe(SparkTags.SecuritiesTag).OnDispatcher().Subscribe(sec_map => dialog.Resources[ResourceKeys.Securities] = sec_map)));
gs_vm.RequestClose += (s, e) => WindowUtilities.force_close(dialog);
return dialog;
}));
@@ -1,15 +1,17 @@
using bts.api;
using bts.settings;
using bts.shell.orderbook;
using bts.spark;
using mbts.persist;
using mbts.spark;
using mbts.ui;
using System;
using System.ComponentModel.Composition;
using System.Reactive.Linq;
using System.Threading.Tasks;
namespace bts.shell {
internal class OrderBookWindowFactory : ShellWindowFactory<OrderBookStartupData> {
class OrderBookWindowFactory : ShellWindowFactory<OrderBookStartupData> {
readonly IBus _bus;
readonly INotificationService _notification_service;
readonly INotificationServiceDataCache _notification_service_data_cache;
@@ -26,9 +28,7 @@ namespace bts.shell {
}
public override Task<IShellWindow> load(OrderBookStartupData state) {
var settings_manager = new WindowSettingsManager<OrderBookGridSettings>(_settings_hub, state.grid_settings_wrapper(),
us => us.order_book_grid ?? OrderBookStartupData.DefaultGrid,
(us, s) => us.with(order_book_grid: s));
var settings_manager = _settings_hub.create_orderbook_settings_manager(state.grid_settings_wrapper());
var w = new OrderBookWindow(_model, this, _bus, _notification_service, state, SparkCommands.ShowOrders.icon, settings_manager.as_non_shared());
return Task.FromResult<IShellWindow>(w);
}
@@ -37,5 +37,17 @@ namespace bts.shell {
var w = await load(startup_data);
_bus.post(SparkTags.ShowWindow, w);
}
[Export(typeof(IOrderBookGridSettingsMigrationRule))]
class EnableBackgroundMigrationRule : IOrderBookGridSettingsMigrationRule {
public bool apply(OrderBookGridSettings settings, out OrderBookGridSettings new_settings) {
if (!settings.general.enable_background_color) {
new_settings = settings.with(general: settings.general.with(enable_background_color: true));
return true;
}
new_settings = default;
return false;
}
}
}
}
@@ -7,6 +7,7 @@ using ProtoBuf;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using GridSettings = mbts.ui.settings.GridSettings;
namespace bts.shell.orderbook {
@@ -42,8 +43,8 @@ namespace bts.shell.orderbook {
public IReadOnlyList<OrderRecordTuplePredicateAnimations> predicate_animations => _predicate_animations ?? (_predicate_animations = new List<OrderRecordTuplePredicateAnimations>());
IEnumerable<IPredicateAnimationsData<OrderSecurity, OrderRecordTuplePredicate>> IGridSettingsWithNotifications<OrderSecurity, OrderRecordTuplePredicate>.predicate_animations => predicate_animations;
public OrderBookGridSettings with(GridSettings general) =>
new OrderBookGridSettings(general ?? this.general, predicate_animations) {
public OrderBookGridSettings with(GridSettings general = null, IEnumerable<OrderRecordTuplePredicateAnimations> predicate_animations = null) =>
new OrderBookGridSettings(general ?? this.general, predicate_animations?.ToList() ?? this.predicate_animations) {
algo_tree_display_mode = algo_tree_display_mode
};
@@ -1,23 +1,27 @@
using bts.api;
using bts.api.orderman;
using mbts;
using mbts.spark;
using mbts.ui;
using mbts.ui.controls;
using mbts.ui.settings;
using mbts.ui.utility;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using Columns = mbts.spark.OrderBookGridView.ColumnKeys;
using CompositeDisposable = System.Reactive.Disposables.CompositeDisposable;
namespace bts.shell.orderbook {
class OrderBookGridSettingsManager : IDisposable, IObserver<OrderBookGridSettings> {
class OrderBookGridSettingsManager : IDisposable {
public static readonly IReadOnlyDictionary<string, GridColumnSettingsFeatureFlags> hard_column_flags;
readonly IObserver<OrderBookGridSettings> _core_man_as_observer;
readonly IEnumerable<OrderBookRowVM> _rows;
readonly ObservableCollection<OrderRecordTuplePredicateAnimations> _active_predicate_animations =
new ObservableCollection<OrderRecordTuplePredicateAnimations>();
IReadOnlyList<OrderRecordTuplePredicateAnimations> _predicate_animations;
AlgoTreeDisplayMode _algo_tree_display_mode;
IDisposable _sound_hooks;
static OrderBookGridSettingsManager() {
// make font-weight immutable for all columns (the font-weight rule is hard-coded: Regular for root-orders, Light for all others)
@@ -27,7 +31,7 @@ namespace bts.shell.orderbook {
// make font-color immutable for Side, Size, and Filled columns (the font-color rule is hard-coded: MarketData.bid for Buy orders, MarketData.ask for Sells)
// TODO: Remove the immutable font-color flags when cell animation is ready
foreach (var c in new[] { Columns.Side, Columns.Size, Columns.Filled }) {
col_settings_flags[c.ToString()] |= GridColumnSettingsFeatureFlags.ImmutableFontWeight;
col_settings_flags[c.ToString()] |= GridColumnSettingsFeatureFlags.ImmutableFontColor;
}
col_settings_flags[Columns.Commit.ToString()] |= GridColumnSettingsFeatureFlags.ImmutableHeader;
hard_column_flags = col_settings_flags;
@@ -36,36 +40,22 @@ namespace bts.shell.orderbook {
public OrderBookGridSettingsManager(
IBus bus,
INotificationService notification_service,
OrdersModel model,
IEnumerable<OrderBookRowVM> rows,
ObservableCollection<DIYGrid.ColumnBase> columns) {
core_manager = new GridSettingsManager<OrderBookRowVM, OrderSecurity, OrderRecordTuplePredicate, OrderBookGridSettings>(
bus, model, rows, columns,
() => new OrderRecordTuplePredicate(),
() => {
var pvm = PredicateVMWrapper.create<OrderSecurity, OrderRecordTuplePredicate, OrderRecordTuplePredicate.PredicateTypeOneofCase>(
OrderRecordTuplePredicateVMFactory.get_oneofs(bus, model), rp => rp.predicate_type_case);
pvm.selected_predicate_type = pvm.predicate_types.FirstOrDefault();
return pvm;
},
col_id => {
var pvm = PredicateVMWrapper.create<OrderSecurity, OrderRecordTuplePredicate, OrderRecordTuplePredicate.PredicateTypeOneofCase>(
OrderRecordTuplePredicateVMFactory.get_oneofs(bus, model), rp => rp.predicate_type_case);
pvm.selected_predicate_type = _corresponding_predicate_type(EnumHelper.parse_or_default<Columns>(col_id));
return pvm;
},
(gen, anims) => new OrderBookGridSettings(gen, anims.empty_if_null().Select(a => new OrderRecordTuplePredicateAnimations(a.predicate_data, a.cell_animations, a.row_animations, a.sound_notifications))) { algo_tree_display_mode = _algo_tree_display_mode },
p => p.create_predicate(notification_service.data_cache),
p => p.create_notification_predicate(notification_service.data_cache),
notification_service == null ? new Func<IPredicate<OrderSecurity>, SoundNotification, IDisposable>((p, sn) => Disposable.empty) : notification_service.link_to_order,
notification_service == null ? new Func<INotificationPredicate<OrderSecurity>, IDisposable>(t => Disposable.empty) : notification_service.link_to_order,
hard_column_flags);
_core_man_as_observer = core_manager;
_rows = rows;
core_manager = new GridSettingsManager(columns, hard_column_flags);
_sound_hooks = ObservableAutoWrapper.connect_item_hooks(
_active_predicate_animations,
a => {
var pred = a.predicate_data.create_predicate(notification_service.data_cache);
return new CompositeDisposable(
a.sound_notifications.Select(s => notification_service.link_to_order(pred, s)));
});
}
public event Action algo_tree_display_mode_changed;
public GridSettingsManager<OrderBookRowVM, OrderSecurity, OrderRecordTuplePredicate, OrderBookGridSettings> core_manager { get; }
public GridSettingsManager core_manager { get; }
public AlgoTreeDisplayMode algo_tree_display_mode {
get => _algo_tree_display_mode;
@@ -78,23 +68,29 @@ namespace bts.shell.orderbook {
}
public void load_from(OrderBookGridSettings gs) {
core_manager.load_from(gs);
core_manager.load_from(gs.general);
algo_tree_display_mode = gs.algo_tree_display_mode;
_predicate_animations = gs.predicate_animations;
_active_predicate_animations.sync_with(_predicate_animations.Where(p => p.is_enabled));
foreach (var r in _rows)
r.raise_invalidated();
}
public OrderBookGridSettings create(IEnumerable<SortingCriteria> sorting_criterion) {
var gs = core_manager.create();
gs.general = gs.general.with(sorting_criterion: sorting_criterion);
gs.algo_tree_display_mode = _algo_tree_display_mode;
return gs;
}
public OrderBookSettingsVM create_vm(OrderBookGridSettings gs) =>
new OrderBookSettingsVM(core_manager.create_vm(gs.general)) {
algo_tree_display_mode = gs.algo_tree_display_mode
public OrderBookGridSettings create(IEnumerable<SortingCriteria> sorting_criterion) =>
new OrderBookGridSettings(
core_manager.create(),
_predicate_animations.ToList()) {
algo_tree_display_mode = _algo_tree_display_mode
};
public void Dispose() => core_manager.Dispose();
public OrderBookSettingsVM create_vm(OrderBookGridSettings gs) {
var vm = new OrderBookSettingsVM(core_manager.create_vm());
vm.load(gs);
return vm;
}
public void Dispose() =>
Disposable.dispose(ref _sound_hooks);
static OrderRecordTuplePredicate.PredicateTypeOneofCase _corresponding_predicate_type(Columns col) {
switch (col) {
@@ -122,12 +118,5 @@ namespace bts.shell.orderbook {
default: return default;
}
}
void IObserver<OrderBookGridSettings>.OnNext(OrderBookGridSettings value) {
_core_man_as_observer.OnNext(value);
algo_tree_display_mode = value.algo_tree_display_mode;
}
void IObserver<OrderBookGridSettings>.OnError(Exception error) => _core_man_as_observer.OnError(error);
void IObserver<OrderBookGridSettings>.OnCompleted() => _core_man_as_observer.OnCompleted();
}
}
@@ -1,46 +1,150 @@
using bts.api;
using bts.api.orderman;
using mbts;
using mbts.spark;
using mbts.ui;
using mbts.ui.settings;
using mbts.ui.shell;
using mbts.ui.spark;
using System;
using System.Collections.Generic;
namespace bts.shell.orderbook {
class OrderBookSettingsVM : DisposableViewModelBase, IWindowSettingsVM<OrderBookGridSettings>, ITemplateSelectorKey {
AlgoTreeDisplayMode _algo_tree_display_mode;
public OrderBookSettingsVM(GridSettingsVM<OrderSecurity, OrderRecordTuplePredicate, OrderBookGridSettings> core_settings_vm) =>
this.core_settings_vm = core_settings_vm;
public OrderBookSettingsVM(
IBus bus,
OrdersModel orders_model,
Func<OrderRecordTuplePredicate> predicate_data_factory,
Func<OrderRecordTuplePredicate, INotificationPredicate<OrderSecurity>> notification_predicate_factory,
Func<IPropertyWrapperVM<OrderRecordTuplePredicate>> create_predicate_vm,
Func<string, IPropertyWrapperVM<OrderRecordTuplePredicate>> create_column_predicate_vm,
Func<GridSettings, IEnumerable<IPredicateAnimationsData<OrderSecurity, OrderRecordTuplePredicate>>, OrderBookGridSettings> create_settings_obj,
IReadOnlyDictionary<string, GridColumnSettingsFeatureFlags> column_flags = null) {
core_settings_vm = new GridSettingsVM<OrderSecurity, OrderRecordTuplePredicate, OrderBookGridSettings>(
bus, orders_model, predicate_data_factory, notification_predicate_factory, create_predicate_vm, create_column_predicate_vm, create_settings_obj, column_flags);
}
public GridSettingsVM<OrderSecurity, OrderRecordTuplePredicate, OrderBookGridSettings> core_settings_vm { get; }
public AlgoTreeDisplayMode algo_tree_display_mode { get => _algo_tree_display_mode; set => Set(ref _algo_tree_display_mode, value); }
string ITemplateSelectorKey.key => GridSettingsTemplateKeys.OrderBook;
public void load(OrderBookGridSettings grid_settings) {
core_settings_vm.load(grid_settings);
algo_tree_display_mode = grid_settings.algo_tree_display_mode;
}
public OrderBookGridSettings get_result() {
var gs = core_settings_vm.get_result();
gs.algo_tree_display_mode = algo_tree_display_mode;
return gs;
}
}
using bts.api;
using bts.utils;
using mbts;
using mbts.spark;
using mbts.ui;
using mbts.ui.shell;
using mbts.ui.spark;
using mbts.ui.viewmodels;
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Windows.Input;
namespace bts.shell.orderbook {
class OrderBookSettingsVM : DisposableViewModelBase, IWindowSettingsVM<OrderBookGridSettings>, ITemplateSelectorKey {
readonly BooleanParameterVM _show_full_algo_tree = new BooleanParameterVM("Show Full Algo Tree");
public OrderBookSettingsVM(GridSettingsVM core_settings_vm) {
this.core_settings_vm = core_settings_vm;
this.echo(_show_full_algo_tree, nameof(algo_tree_display_mode));
add_alert_cmd = new DelegateCommand(() => {
alerts.Add(new OrderAlertVM {
is_enabled = true,
predicate_type = OrderRecordTuplePredicate.PredicateTypeOneofCase.FilledEvent,
sound_type = SoundNotification.SystemSound.BellCounter
});
});
delete_alert_cmd = new DelegateCommand<OrderAlertVM>(vm => {
if (vm != null)
alerts.Remove(vm);
});
}
public GridSettingsVM core_settings_vm { get; }
public AlgoTreeDisplayMode algo_tree_display_mode {
get => _show_full_algo_tree.value ? AlgoTreeDisplayMode.FullTree : AlgoTreeDisplayMode.RootAndLeafNodes;
set => _show_full_algo_tree.value = value == AlgoTreeDisplayMode.FullTree;
}
public IEnumerable parameters {
get {
yield return _show_full_algo_tree;
}
}
public ICommand add_alert_cmd { get; }
public ICommand delete_alert_cmd { get; }
// TODO: make this generic (for now, all views orderbook settings can ignore it)
public ObservableCollection<OrderAlertVM> alerts { get; } = new ObservableCollection<OrderAlertVM>();
string ITemplateSelectorKey.key => GridSettingsTemplateKeys.OrderBook;
public void load(OrderBookGridSettings grid_settings) {
core_settings_vm.load(grid_settings.general);
algo_tree_display_mode = grid_settings.algo_tree_display_mode;
CollectionHelper.add_or_update_existing(alerts,
grid_settings.predicate_animations,
pa => {
var vm = new OrderAlertVM();
vm.load_from(pa);
return vm;
},
(vm, pa) => vm.load_from(pa));
}
public OrderBookGridSettings get_result() {
var gs = core_settings_vm.get_result();
return new OrderBookGridSettings(gs, alerts.Select(vm => vm.create())) {
algo_tree_display_mode = algo_tree_display_mode
};
}
}
class OrderAlertVM : ViewModelBase, IValueFactory<OrderRecordTuplePredicateAnimations> {
SecurityCriteria _security_criteria;
SoundNotification.SystemSound _sound_type;
OrderRecordTuplePredicate.PredicateTypeOneofCase _predicate_type;
bool _is_enabled;
public bool is_enabled { get => _is_enabled; set => Set(ref _is_enabled, value); }
public SecurityCriteria security_criteria { get => _security_criteria; set => Set(ref _security_criteria, value); }
public SoundNotification.SystemSound sound_type { get => _sound_type; set => Set(ref _sound_type, value); }
public OrderRecordTuplePredicate.PredicateTypeOneofCase predicate_type { get => _predicate_type; set => Set(ref _predicate_type, value); }
public IEnumerable<OrderRecordTuplePredicate.PredicateTypeOneofCase> predicate_types { get; } =
new[] {
OrderRecordTuplePredicate.PredicateTypeOneofCase.FilledEvent,
OrderRecordTuplePredicate.PredicateTypeOneofCase.CompletedEvent,
OrderRecordTuplePredicate.PredicateTypeOneofCase.CanceledEvent,
OrderRecordTuplePredicate.PredicateTypeOneofCase.SentEvent
};
public OrderRecordTuplePredicateAnimations create() {
var pred = new OrderRecordTuplePredicate();
switch (predicate_type) {
case OrderRecordTuplePredicate.PredicateTypeOneofCase.CanceledEvent:
pred.canceled_event = new OrderCanceledEventPredicate {
security = security_criteria.is_defined() ?
new SecurityPredicate { is_value = security_criteria } : null
};
break;
case OrderRecordTuplePredicate.PredicateTypeOneofCase.FilledEvent:
pred.filled_event = new OrderFilledEventPredicate {
security = security_criteria.is_defined() ?
new SecurityPredicate { is_value = security_criteria } : null
};
break;
case OrderRecordTuplePredicate.PredicateTypeOneofCase.CompletedEvent:
pred.completed_event = new OrderCompletedEventPredicate {
security = security_criteria.is_defined() ?
new SecurityPredicate { is_value = security_criteria } : null
};
break;
case OrderRecordTuplePredicate.PredicateTypeOneofCase.SentEvent:
pred.sent_event = new OrderSentEventPredicate {
security = security_criteria.is_defined() ?
new SecurityPredicate { is_value = security_criteria } : null
};
break;
}
return new OrderRecordTuplePredicateAnimations(
pred, null, null,
new[] {
new SoundNotification { system_sound = sound_type }
},
is_enabled);
}
public void load_from(OrderRecordTuplePredicateAnimations value) {
if (value.sound_notifications.Count == 0)
return;
is_enabled = value.is_enabled;
var animation = value.sound_notifications[0];
var pred = value.predicate_data;
if (animation.sound_type_case == SoundNotification.SoundTypeOneofCase.System)
sound_type = animation.system_sound.Value;
if (pred != null) {
predicate_type = pred.predicate_type_case;
switch (pred.predicate_type_case) {
case OrderRecordTuplePredicate.PredicateTypeOneofCase.CanceledEvent:
security_criteria = pred.canceled_event.security?.is_value;
break;
case OrderRecordTuplePredicate.PredicateTypeOneofCase.CompletedEvent:
security_criteria = pred.completed_event.security?.is_value;
break;
case OrderRecordTuplePredicate.PredicateTypeOneofCase.FilledEvent:
security_criteria = pred.filled_event.security?.is_value;
break;
}
}
}
}
}
@@ -0,0 +1,150 @@
<UserControl
x:Class="mbts.ui.spark.OrderBookSettingsView"
x:ClassModifier="internal"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:shell="clr-namespace:bts.shell"
xmlns:orderbook="clr-namespace:bts.shell.orderbook"
xmlns:u="clr-namespace:mbts.ui"
xmlns:c="clr-namespace:mbts.ui.controls"
xmlns:usp="clr-namespace:mbts.ui.spark"
xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
Content="{Binding}"
>
<UserControl.Resources>
<usp:GridSettingsResources/>
</UserControl.Resources>
<UserControl.ContentTemplate>
<DataTemplate DataType="{x:Type orderbook:OrderBookSettingsVM}">
<Grid>
<Grid.Margin>
<Thickness Left="{StaticResource HorizontalPush2}" Right="{StaticResource HorizontalPush2}"/>
</Grid.Margin>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<c:ActionMenu
HorizontalAlignment="Right"
Margin="0,2,0,0"
ItemsSource="{Binding DataContext.action_menu_items, RelativeSource={RelativeSource AncestorType=usp:ShellSettingsDialog}}"
Style="{StaticResource SparkToolBarItem}"
/>
<TabControl x:Name="Tabs" HorizontalAlignment="Stretch" MinHeight="480" MinWidth="500" Margin="0,2,0,0" Padding="6">
<TabControl.ItemContainerStyle>
<Style TargetType="TabItem">
<Setter Property="MinWidth" Value="60"/>
<Setter Property="HorizontalAlignment" Value="Center"/>
</Style>
</TabControl.ItemContainerStyle>
<TabItem Header="Grid">
<ItemsControl x:Name="ParameterItemsControl" Grid.IsSharedSizeScope="True" Margin="8,16,0,0">
<ItemsControl.ItemsSource>
<CompositeCollection>
<CollectionContainer Collection="{Binding DataContext.parameters, Source={u:DirectReference ParameterItemsControl}}"/>
<CollectionContainer Collection="{Binding DataContext.core_settings_vm.parameters, Source={u:DirectReference ParameterItemsControl}}"/>
</CompositeCollection>
</ItemsControl.ItemsSource>
</ItemsControl>
</TabItem>
<TabItem Header="Columns" Content="{Binding core_settings_vm}" ContentTemplate="{StaticResource ColumnsTabContent}"/>
<TabItem Header="Alerts" DataContext="{Binding DataContext.instance_vm, RelativeSource={RelativeSource AncestorType=usp:ShellSettingsDialog}}">
<Grid VerticalAlignment="Top">
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<ScrollViewer HorizontalScrollBarVisibility="Hidden" VerticalScrollBarVisibility="Auto">
<c:DIYGrid
Rows="{Binding alerts}"
BorderBrush="Black"
BorderThickness="1"
RowsHorizontalScrollBarVisibility="Collapsed"
SelectionMode="Single"
>
<c:DIYGrid.Columns>
<c:DIYGridColumnCollection>
<c:DIYGridColumn key="Delete" HeaderContainerStyle="{StaticResource DisabledColumnHeader}" Width="23" is_visible="True"/>
<c:DIYGridColumn key="IsEnabled" HeaderContainerStyle="{StaticResource DisabledColumnHeader}" Width="66" is_visible="True"/>
<c:DIYGridColumn key="Security" Header="Security" Width="130" is_visible="True"/>
<c:DIYGridColumn key="EventType" Header="Event Type" Width="120" is_visible="True"/>
<c:DIYGridColumn key="Sound" Header="Sound" Width="130" is_visible="True"/>
</c:DIYGridColumnCollection>
</c:DIYGrid.Columns>
<c:DIYGrid.RowTemplate>
<DataTemplate>
<Grid c:DIYGrid.SyncWithColumnGeometry="True">
<Button
c:DIYGrid.ColumnKey="Delete"
HorizontalAlignment="Center"
Command="{Binding DataContext.delete_alert_cmd, RelativeSource={RelativeSource AncestorType=c:DIYGrid}}"
CommandParameter="{Binding}"
Style="{StaticResource RoundCloseButton}"
ToolTip="Delete"
/>
<ToggleButton
c:DIYGrid.ColumnKey="IsEnabled"
IsChecked="{Binding is_enabled}"
Style="{StaticResource ToggleSwitch}"
/>
<ComboBox
c:DIYGrid.ColumnKey="EventType"
Margin="{StaticResource CellPadding}"
ItemsSource="{Binding predicate_types}"
ItemTemplate="{StaticResource EnumDescriptionTemplate}"
SelectedItem="{Binding predicate_type}"
/>
<c:ContractPickerPopup
c:DIYGrid.ColumnKey="Security"
Margin="{StaticResource CellPadding}"
AllowEmpty="True"
CloseOnOK="True"
CloseOnCancel="True"
MaxWidth="180"
DataContext="{Binding predicate}"
SecurityMap="{DynamicResource {x:Static u:ResourceKeys.Securities}}"
Header="{Binding security_criteria}"
ShowDialogSkirt="True"
ToolTip="{Binding security_criteria, Converter={x:Static c:ContractPickerPopup.SecurityCriteriaTextConverter}, Mode=OneWay}"
OKClick="{x:Static c:ContractPickerPopup.FlushSelectedCriteriaHandler}"
SelectedCriteria="{Binding security_criteria, UpdateSourceTrigger=Explicit}"
>
<c:ContractPickerPopup.HeaderTemplate>
<DataTemplate>
<TextBlock Text="{Binding Converter={x:Static c:ContractPickerPopup.SecurityCriteriaTextConverter}}" TextTrimming="CharacterEllipsis"/>
</DataTemplate>
</c:ContractPickerPopup.HeaderTemplate>
</c:ContractPickerPopup>
<ComboBox
x:Name="sound_dropdown"
c:DIYGrid.ColumnKey="Sound"
Margin="{StaticResource CellPadding}"
ItemsSource="{u:EnumBindingSource Type={u:Nested shell:SoundNotification.SystemSound}, Except=None}"
ItemTemplate="{StaticResource EnumDescriptionTemplate}"
SelectedItem="{Binding sound_type}"
>
<i:Interaction.Triggers>
<i:EventTrigger EventName="DropDownClosed">
<i:InvokeCommandAction Command="{x:Static c:SoundAnimationView.PreviewSoundCommand}" CommandParameter="{Binding SelectedItem, ElementName=sound_dropdown}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</ComboBox>
<Rectangle Grid.Column="0" HorizontalAlignment="Right" Width="1" Fill="Gray"/>
<Rectangle Grid.Column="1" HorizontalAlignment="Right" Width="1" Fill="Gray"/>
<Rectangle Grid.Column="2" HorizontalAlignment="Right" Width="1" Fill="Gray"/>
<Rectangle Grid.Column="3" HorizontalAlignment="Right" Width="1" Fill="Gray"/>
</Grid>
</DataTemplate>
</c:DIYGrid.RowTemplate>
</c:DIYGrid>
</ScrollViewer>
<Button Grid.Row="1" HorizontalAlignment="Stretch" HorizontalContentAlignment="Center" VerticalAlignment="Top" VerticalContentAlignment="Center" Command="{Binding add_alert_cmd}">
<TextBlock Margin="8,4" FontWeight="SemiBold">Add Alert</TextBlock>
</Button>
</Grid>
</TabItem>
</TabControl>
</Grid>
</DataTemplate>
</UserControl.ContentTemplate>
</UserControl>
@@ -0,0 +1,35 @@
using mbts.ui.converters;
using System.Windows.Controls;
using System.Windows.Data;
namespace mbts.ui.spark {
/// <summary>
/// Interaction logic for OrderBookSettingsView.xaml
/// </summary>
partial class OrderBookSettingsView : UserControl {
public static readonly IValueConverter
DisplayNameVisualStateConverter = ValueConverter.create<bool, string>(can_override => can_override ? VisualStateNames.DisplayName.Active : VisualStateNames.DisplayName.Inactive),
ColumnVisibilityToggleVisualStateConverter = ValueConverter.create<bool, string>(can_override => can_override ? VisualStateNames.ColumnVisibilityToggle.Active : VisualStateNames.ColumnVisibilityToggle.Inactive);
public OrderBookSettingsView() =>
InitializeComponent();
public static class VisualStateNames {
public static class DisplayName {
public const string GroupName = nameof(DisplayName);
public static readonly string
Active = $"{GroupName}{VisualStates.Active}",
Inactive = $"{GroupName}{VisualStates.Inactive}";
}
public static class ColumnVisibilityToggle {
public const string GroupName = nameof(ColumnVisibilityToggle);
public static readonly string
Active = $"{GroupName}{VisualStates.Active}",
Inactive = $"{GroupName}{VisualStates.Inactive}";
}
}
}
}
@@ -1,133 +1,342 @@
using bts.collections;
using mbts.spark;
using mbts.ui.controls;
using mbts.ui.settings;
using mbts.ui.shell;
using mbts.utils;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Linq;
using System.Reactive.Linq;
using System.Windows;
namespace bts.shell.pnl {
class PNLColumnVM : ViewModelBase, ISimpleColumnVM {
readonly SimpleGridSettingsVM _owner;
readonly PNLView.Columns _pnl_id;
object _selected_item;
string _display_name;
bool _is_visible;
double _width;
HorizontalAlignment _horizontal_alignment;
public PNLColumnVM(SimpleGridSettingsVM owner, PNLView.Columns id) {
_owner = owner;
_pnl_id = id;
default_column_vm.PropertyChanged += echo;
void echo(object sender, PropertyChangedEventArgs e) {
switch (e.PropertyName) {
case nameof(is_font_weight_overridden):
case nameof(is_font_color_overridden):
RaisePropertyChanged(e.PropertyName);
break;
}
}
}
public PNLCellAppearanceOverrideVM default_column_vm { get; } = new PNLCellAppearanceOverrideVM();
public PNLCellAppearanceOverrideVM group_column_vm { get; } = new PNLCellAppearanceOverrideVM();
public PNLCellAppearanceOverrideVM header_column_vm { get; } = new PNLCellAppearanceOverrideVM();
//
public ObservableCollection<PNLRuleColumnVM> rule_columns { get; } = new ObservableCollection<PNLRuleColumnVM>();
public object selected_item {
get => _selected_item;
set {
if (value != null)
Set(ref _selected_item, value);
}
}
public string id { get => _pnl_id.ToString(); set => Log.assert(value == _pnl_id.ToString()); }
public string display_name { get => _display_name; set => Set(ref _display_name, value); }
public double width { get => _width; set => Set(ref _width, value); }
public bool is_visible { get => _is_visible; set => Set(ref _is_visible, value); }
public bool is_font_color_overridden {
get => default_column_vm.is_font_color_overridden;
set => default_column_vm.is_font_color_overridden = value;
}
public HorizontalAlignment horizontal_alignment { get => _horizontal_alignment; set => Set(ref _horizontal_alignment, value); }
public ColorPickerPopupVM font_color_override => default_column_vm.font_color_override;
public bool is_font_weight_overridden { get => default_column_vm.is_font_weight_overridden; set => default_column_vm.is_font_weight_overridden = value; }
public FontWeight font_weight_override { get => default_column_vm.font_weight_override; set => default_column_vm.font_weight_override = value; }
public void apply_flags(GridColumnSettingsFeatureFlags flags) { }
public GridColumnSettings create() {
var result = new GridColumnSettings(id: id, display_name: display_name, is_visible: is_visible, horizontal_alignment: horizontal_alignment, width: width);
result = default_column_vm.apply_to(result);
return result;
}
public void load_from(GridColumnSettings value) {
_assert_id(value.id);
default_column_vm.load(value);
display_name = value.display_name;
width = value.width;
is_visible = value.is_visible;
horizontal_alignment = value.horizontal_alignment;
}
public void load_from(GridColumnSettings value, IReadOnlyDictionary<string, PNLAnimation> triggers, GridColumnSettings group, IReadOnlyDictionary<string, PNLAnimation> group_triggers, IReadOnlyDictionary<string, PNLAnimation> header_triggers, GridColumnSettings header) {
_assert_id(value.id);
load_from(value);
if (group != null)
group_column_vm.load(group);
if (header != null)
header_column_vm.load(header);
rule_columns.SetItems(triggers.Select(kv => {
var rule_col = new PNLRuleColumnVM(kv.Key);
rule_col.load_from(kv.Value, group_triggers.GetValueOrDefault(kv.Key), header_triggers.GetValueOrDefault(kv.Key));
return rule_col;
}));
}
public IEnumerable<(string expression, PNLAnimation row, PNLAnimation group, PNLAnimation header)> get_triggers() {
foreach (var col in rule_columns)
yield return (col.rule_text, col.row_params.to_animation(_pnl_id), col.group_params.to_animation(_pnl_id), col.header_params.to_animation(_pnl_id));
}
void _assert_id(string id) =>
Log.assert(id == _pnl_id.ToString(), $"PNLColumn id string '{id}' does not match the expected {typeof(PNLView.Columns)} key '{_pnl_id}.'");
public override string ToString() => "Default";
}
class PNLRuleColumnVM : ViewModelBase, IEquatable<PNLRuleColumnVM> {
public PNLRuleColumnVM(string rule_text) =>
this.rule_text = rule_text;
public string rule_text { get; }
public PNLCellAppearanceOverrideVM row_params { get; } = new PNLCellAppearanceOverrideVM();
public PNLCellAppearanceOverrideVM group_params { get; } = new PNLCellAppearanceOverrideVM();
public PNLCellAppearanceOverrideVM header_params { get; } = new PNLCellAppearanceOverrideVM();
public void load_from(PNLAnimation row, PNLAnimation group, PNLAnimation header) {
row_params.load(row);
group_params.load(group);
header_params.load(header);
}
public bool Equals(PNLRuleColumnVM other) =>
other != null && rule_text == other.rule_text;
public override bool Equals(object obj) => Equals(obj as PNLRuleColumnVM);
public override int GetHashCode() => rule_text.GetHashCode();
public override string ToString() => rule_text;
}
using bts.collections;
using bts.proto;
using mbts;
using mbts.spark;
using mbts.ui;
using mbts.ui.controls;
using mbts.ui.settings;
using mbts.ui.utilities;
using mbts.ui.utility;
using mbts.ui.viewmodels;
using mbts.utils;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Reactive.Linq;
using System.Windows;
using System.Windows.Media;
namespace bts.shell.pnl {
class PNLColumnSettingsModeVM : GridColumnSettingsVM {
readonly ObservableAggregateCollection<object> _aggregate_parameters = new ObservableAggregateCollection<object>();
IDisposable _param_sync;
List<(string rule, PNLCellModeVM vm)> _rules = new List<(string rule, PNLCellModeVM vm)>();
public PNLColumnSettingsModeVM(string mode) {
this.mode = mode;
show_horizontal_alignment = false;
show_width = false;
can_edit_header = false;
header.can_override = false;
parameter_group = new ParameterGroupVM(mode);
_param_sync = ObservableAutoWrapper.connect(parameters, parameter_group.parameters);
}
public ParameterGroupVM parameter_group { get; }
public string mode { get; }
public IEnumerable<(string rule, PNLCellModeVM vm)> rules => _rules;
public GridColumnSettings apply_to(GridColumnSettings target) {
return target.with(
font_weight_override: new NullableFontWeightProto(font_weight.effective_is_overridden ? font_weight.override_value : (FontWeight?)null),
font_color_override: new NullableColorProto(font_color.effective_is_overridden ? font_color.override_value : (Color?)null),
background_color_override: new NullableColorProto(background.effective_is_overridden ? background.override_value : (Color?)null));
}
public void load_rules(IReadOnlyDictionary<string, PNLAnimation> rules) {
Disposable.dispose(ref _param_sync);
_rules = rules.Select(
kv => {
var rule_vm = new PNLCellModeVM($"When {DisplayNameHelper.expression_snake_to_title_case(kv.Key)}", kv.Key);
rule_vm.load(kv.Value);
return (kv.Key, rule_vm);
})
.ToList();
if (_rules.Count > 0) {
var row_defaults = new PNLCellModeVM("Default") { is_expanded = true };
parameter_group.parameters.SetItems(_rules.Select(r => r.vm).Prepend(row_defaults));
} else {
sync_params();
_param_sync = ObservableAutoWrapper.connect(parameters, parameter_group.parameters);
}
}
}
class PNLRulesVM : ParameterGroupVM {
PNLCellModeRuleSetVM _selected_cell_mode;
public PNLRulesVM(string display_name) : base(display_name) {
}
public PNLCellModeRuleSetVM selected_cell_mode { get => _selected_cell_mode; set => Set(ref _selected_cell_mode, value); }
public void load_from(GridColumnSettings value, IReadOnlyDictionary<string, PNLAnimation> triggers, GridColumnSettings group, IReadOnlyDictionary<string, PNLAnimation> group_triggers, IReadOnlyDictionary<string, PNLAnimation> header_triggers, GridColumnSettings header) {
parameters.Clear();
var default_mode = new PNLCellModeRuleSetVM("Default");
default_mode.default_vm.load(value);
default_mode.group_vm.load(group);
default_mode.header_vm.load(header);
parameters.Add(default_mode);
foreach (var kv in triggers) {
var rule_vm = new PNLCellModeRuleSetVM($"When {DisplayNameHelper.expression_snake_to_title_case(kv.Key)}", kv.Key);
rule_vm.default_vm.load(kv.Value);
if (group_triggers.TryGetValue(kv.Key, out var g)) {
rule_vm.group_vm.load(g);
}
if (header_triggers.TryGetValue(kv.Key, out var h))
rule_vm.header_vm.load(h);
parameters.Add(rule_vm);
}
if (selected_cell_mode == null)
selected_cell_mode = default_mode;
}
}
class PNLCellModeRuleSetVM : ParameterGroupVM {
PNLCellRuleModeVM _selected_cell_mode;
public PNLCellModeRuleSetVM(string display_name, string rule = null) : base(display_name) {
parameters.AddRange(default_vm, group_vm, header_vm);
_selected_cell_mode = default_vm;
}
public PNLCellRuleModeVM selected_cell_mode { get => _selected_cell_mode; set => Set(ref _selected_cell_mode, value); }
public PNLCellRuleModeVM default_vm { get; } = new PNLCellRuleModeVM("Row") { is_expanded = true };
public PNLCellRuleModeVM group_vm { get; } = new PNLCellRuleModeVM("Group Row");
public PNLCellRuleModeVM header_vm { get; } = new PNLCellRuleModeVM("Header Row");
}
class PNLCellRuleModeVM : ParameterGroupVM {
public PNLCellRuleModeVM(string display_name, string rule = null) : base(display_name) {
parameters.AddRange(background, font_color, font_weight);
background.can_override = true;
font_color.can_override = true;
font_weight.can_override = true;
}
public ColorOverrideParameterVM background { get; } = new ColorOverrideParameterVM("Background");
public ColorOverrideParameterVM font_color { get; } = new ColorOverrideParameterVM("Font Color");
public FontWeightOverrideParameterVM font_weight { get; } = new FontWeightOverrideParameterVM("Font Weight");
public GridColumnSettings apply_to(GridColumnSettings target) =>
target.with(
font_weight_override: new NullableFontWeightProto(font_weight.effective_is_overridden ? font_weight.override_value : (FontWeight?)null),
font_color_override: new NullableColorProto(font_color.effective_is_overridden ? font_color.override_value : (Color?)null),
background_color_override: new NullableColorProto(background.effective_is_overridden ? background.override_value : (Color?)null));
public PNLAnimation create(PNLView.Columns col_key) {
var rv = new PNLAnimation {
column = col_key
};
if (background.effective_is_overridden)
rv.background = background.override_value.rgb_color();
if (font_color.effective_is_overridden)
rv.foreground = font_color.override_value.rgb_color();
if (font_weight.effective_is_overridden)
rv.font_weight = font_weight.override_value.ToOpenTypeWeight();
return rv;
}
public void load(GridColumnSettings value) {
// font weight
font_weight.is_overridden = value.font_weight_override.HasValue;
if (font_weight.is_overridden)
font_weight.override_value = value.font_weight_override.Value;
// font color
font_color.is_overridden = value.font_color_override.HasValue;
if (font_color.is_overridden)
font_color.override_value = value.font_color_override.Value;
// background
background.is_overridden = value.background_color_override.HasValue;
if (background.is_overridden)
background.override_value = value.background_color_override.Value;
}
public void load(PNLAnimation value) {
// font weight
font_weight.is_overridden = value.font_weight.HasValue && value.font_weight.Value > 0;
if (font_weight.is_overridden)
font_weight.override_value = FontWeight.FromOpenTypeWeight(value.font_weight.Value);
// font color
font_color.is_overridden = value.foreground.HasValue;
if (font_color.is_overridden)
font_color.override_value = value.foreground.Value.argb_color();
// background
background.is_overridden = value.background.HasValue;
if (background.is_overridden)
background.override_value = value.background.Value.argb_color();
}
}
class PNLCellModeVM : ParameterGroupVM {
public PNLCellModeVM(string display_name, string rule = null) : base(display_name) {
parameters.AddRange(background, font_color, font_weight);
background.can_override = true;
font_color.can_override = true;
font_weight.can_override = true;
}
public string rule { get; }
public ColorOverrideParameterVM background { get; } = new ColorOverrideParameterVM("Background");
public ColorOverrideParameterVM font_color { get; } = new ColorOverrideParameterVM("Font Color");
public FontWeightOverrideParameterVM font_weight { get; } = new FontWeightOverrideParameterVM("Font Weight");
public GridColumnSettings apply_to(GridColumnSettings target) =>
target.with(
font_weight_override: new NullableFontWeightProto(font_weight.effective_is_overridden ? font_weight.override_value : (FontWeight?)null),
font_color_override: new NullableColorProto(font_color.effective_is_overridden ? font_color.override_value : (Color?)null),
background_color_override: new NullableColorProto(background.effective_is_overridden ? background.override_value : (Color?)null));
public PNLAnimation create(PNLView.Columns col_key) {
var rv = new PNLAnimation {
column = col_key
};
if (background.effective_is_overridden)
rv.background = background.override_value.rgb_color();
if (font_color.effective_is_overridden)
rv.foreground = font_color.override_value.rgb_color();
if (font_weight.effective_is_overridden)
rv.font_weight = font_weight.override_value.ToOpenTypeWeight();
return rv;
}
public void load(PNLAnimation value) {
// font weight
font_weight.is_overridden = value.font_weight.HasValue && value.font_weight.Value > 0;
if (font_weight.is_overridden)
font_weight.override_value = FontWeight.FromOpenTypeWeight(value.font_weight.Value);
// font color
font_color.is_overridden = value.foreground.HasValue;
if (font_color.is_overridden)
font_color.override_value = value.foreground.Value.argb_color();
// background
background.is_overridden = value.background.HasValue;
if (background.is_overridden)
background.override_value = value.background.Value.argb_color();
}
}
class PNLColumnSettingsVM : GridColumnSettingsVM {
readonly ObservableCollection<PNLColumnSettingsModeVM> _modes = new ObservableCollection<PNLColumnSettingsModeVM>();
readonly ParameterGroupVM _column_params_group = new ParameterGroupVM("Column Layout");
readonly ObservableAggregateCollection<object> _cell_appearance_groups = new ObservableAggregateCollection<object>();
public PNLColumnSettingsVM() : base(null) {
_column_params_group.parameters.AddRange(header, width, horizontal_alignment);
// hide header/width/align params in group/header vms
group_column_vm.can_edit_width = false;
group_column_vm.can_edit_header = false;
group_column_vm.show_horizontal_alignment = false;
group_column_vm.header.can_override = false;
group_column_vm.sync_params();
// header
header_column_vm.show_width = false;
header_column_vm.show_horizontal_alignment = false;
header_column_vm.header.can_override = false;
header_column_vm.sync_params();
// header
default_vm.show_width = false;
default_vm.show_horizontal_alignment = false;
default_vm.header.can_override = false;
default_vm.sync_params();
default_vm.parameter_group.is_expanded = true;
fonts_and_colors.parameters.Add(default_vm.parameter_group);
fonts_and_colors.parameters.Add(group_column_vm.parameter_group);
fonts_and_colors.parameters.Add(header_column_vm.parameter_group);
apply_flags(GridColumnSettingsFeatureFlags.None);
sync_params();
}
public ParameterGroupVM column_layout => _column_params_group;
public ParameterGroupVM fonts_and_colors { get; } = new ParameterGroupVM("Fonts/Colors");
public PNLColumnSettingsModeVM group_column_vm { get; } = new PNLColumnSettingsModeVM("Group Row");
public PNLColumnSettingsModeVM header_column_vm { get; } = new PNLColumnSettingsModeVM("Header Row");
public PNLColumnSettingsModeVM default_vm { get; } = new PNLColumnSettingsModeVM("Row");
protected override IEnumerable<object> _visible_parameters {
get {
yield return header;
yield return width;
yield return horizontal_alignment;
//yield return default_vm.parameter_group;
//yield return group_column_vm.parameter_group;
//yield return header_column_vm.parameter_group;
}
}
public void load_from(GridColumnSettings value, IReadOnlyDictionary<string, PNLAnimation> triggers, GridColumnSettings group, IReadOnlyDictionary<string, PNLAnimation> group_triggers, IReadOnlyDictionary<string, PNLAnimation> header_triggers, GridColumnSettings header) {
load_from(value);
if (group != null)
group_column_vm.load_from(group);
if (header != null)
header_column_vm.load_from(header);
default_vm.load_rules(triggers);
group_column_vm.load_rules(triggers);
header_column_vm.load_rules(triggers);
if (triggers == null || triggers.Count == 0) {
fonts_and_colors.parameters.SetItems(new[] { default_vm.parameter_group, group_column_vm.parameter_group, header_column_vm.parameter_group });
} else {
fonts_and_colors.parameters.Clear();
//var default_mode = new PNLCellModeRuleSetVM("Default");
//default_mode.default_vm.load(value);
//default_mode.group_vm.load(group);
//default_mode.header_vm.load(header);
//fonts_and_colors.parameters.Add(default_mode);
//foreach (var kv in triggers) {
// var rule_vm = new PNLCellModeRuleSetVM( $"When {DisplayNameHelper.expression_snake_to_title_case(kv.Key)}", kv.Key);
// rule_vm.default_vm.load(kv.Value);
// if (group_triggers.TryGetValue(kv.Key, out var g)) {
// rule_vm.group_vm.load(g);
// }
// if (header_triggers.TryGetValue(kv.Key, out var h))
// rule_vm.header_vm.load(h);
// fonts_and_colors.parameters.Add(rule_vm);
//}
var rule_set = new PNLRulesVM("Rules Poop");
rule_set.load_from(value, triggers, group, group_triggers, header_triggers, header);
fonts_and_colors.parameters.Add(rule_set);
}
}
public IEnumerable<(string expression, PNLAnimation row, PNLAnimation group, PNLAnimation header)> get_triggers() {
var triggers = new Dictionary<string, (PNLAnimation row, PNLAnimation group, PNLAnimation header)>();
if (Enum.TryParse(id, out PNLView.Columns col_key)) {
foreach (var (rule, vm) in default_vm.rules)
triggers[rule] = (vm.create(col_key), null, null);
foreach (var (rule, vm) in group_column_vm.rules)
triggers.AddOrUpdate(rule, r => (null, vm.create(col_key), null), (r, tup) => (tup.row, vm.create(col_key), tup.header));
foreach (var (rule, vm) in header_column_vm.rules)
triggers.AddOrUpdate(rule, r => (null, null, vm.create(col_key)), (r, tup) => (tup.row, tup.group, vm.create(col_key)));
}
return triggers.Select(kv => (kv.Key, kv.Value.row, kv.Value.group, kv.Value.header));
}
public override string ToString() => "Default";
static IEnumerable<object> _standard_params() {
var header = new StringOverrideParameterVM("Header");
var width = new DoubleParameterVM("Width");
var horizontal_alignment = new HorizontalAlignmentParameterVM("Horizontal Alignment");
yield return header;
yield return width;
yield return horizontal_alignment;
}
static IEnumerable<object> _cell_appearance_params() {
var background = new ColorOverrideParameterVM("Background");
var font_color = new ColorOverrideParameterVM("Font Color");
var font_weight = new FontWeightOverrideParameterVM();
yield return background;
yield return font_color;
yield return font_weight;
}
}
}
@@ -1,68 +1,76 @@
using bts.collections;
using bts.utils;
using mbts;
using mbts.collections;
using mbts.expression;
using mbts.spark;
using mbts.ui;
using mbts.ui.settings;
using mbts.ui.shell;
using mbts.ui.spark;
using mbts.ui.utility;
using mbts.utils;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.Dynamic;
using System.Linq;
using ColumnKeys = bts.shell.pnl.PNLView.Columns;
namespace bts.shell.pnl {
class PNLSettingsVM : DisposableViewModelBase, IWindowSettingsVM<PNLSettings>, IValueFactory<PNLSettings>, IObservable<PNLSettings>, ITemplateSelectorKey {
readonly IReadOnlyDictionary<ColumnKeys, AnchorItemVM> _anchor_cache;
readonly DeferrableActionWrapper _sync_anchors;
PNLCalculationType _calculation_type;
PNLHeaderRowDisplay _header_row_display;
PNLSettings _last_loaded;
public PNLSettingsVM(PNLSettings settings) {
// init anchor cache, deferrable resync
_anchor_cache = PNLSettings.anchorable_columns.ToDictionary(k => k, k => new AnchorItemVM(this, k));
_sync_anchors = new DeferrableActionWrapper(() => {
for (int i = 0; i < anchor_items.Count; i++) {
anchor_items[i].anchor_index = i + 1;
anchor_items[i].refresh();
}
});
core_settings_vm = new SimpleGridSettingsVM(column_factory: col_settings => {
var col_vm = new PNLColumnVM(core_settings_vm, EnumHelper.parse<ColumnKeys>(col_settings.id));
col_vm.load_from(col_settings);
return col_vm;
});
core_settings_vm.selection_highlight.bind_opacity_to_alpha = true;
_dispose = Disposable.Create(
anchor_items.Subscribe((s, e) => _sync_anchors.execute()));
load(settings);
}
public IEnumerable<AnchorItemVM> anchor_item_headers => _anchor_cache.Values;
public ObservableCollection<AnchorItemVM> anchor_items { get; } = new ObservableCollection<AnchorItemVM>();
public SimpleGridSettingsVM core_settings_vm { get; }
public PNLGroupRowAppearanceVM group_row_defaults { get; } = new PNLGroupRowAppearanceVM();
public PNLCellAppearanceVM header_row_defaults { get; } = new PNLCellAppearanceVM();
public PNLCalculationType calculation_type { get => _calculation_type; set => Set(ref _calculation_type, value); }
public IEnumerable<PNLCalculationType> calculation_types => EnumHelper.get_values_except(PNLCalculationType.None, PNLCalculationType.Same, PNLCalculationType.Opposite);
public PNLHeaderRowDisplay header_row_display { get => _header_row_display; set => Set(ref _header_row_display, value); }
public IEnumerable<PNLHeaderRowDisplay> header_row_display_options => PNLSettings.header_row_display_options;
string ITemplateSelectorKey.key => GridSettingsTemplateKeys.PNL;
public PNLSettings get_result() {
var s = _last_loaded == null ? new PNLSettings() : SparkUtils.clone_proto(_last_loaded);
s.calculation_type = _calculation_type;
s.grid = core_settings_vm.create();
using bts.collections;
using bts.utils;
using mbts;
using mbts.collections;
using mbts.expression;
using mbts.spark;
using mbts.ui;
using mbts.ui.settings;
using mbts.ui.shell;
using mbts.ui.spark;
using mbts.ui.utility;
using mbts.utils;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.Linq;
using ColumnKeys = bts.shell.pnl.PNLView.Columns;
namespace bts.shell.pnl {
enum CellTypes {
Row,
Group,
Header
}
class PNLSettingsVM : DisposableViewModelBase, IWindowSettingsVM<PNLSettings>, IValueFactory<PNLSettings>, IObservable<PNLSettings>, ITemplateSelectorKey {
readonly IReadOnlyDictionary<ColumnKeys, AnchorItemVM> _anchor_cache;
readonly DeferrableActionWrapper _sync_anchors;
PNLCalculationType _calculation_type;
PNLHeaderRowDisplay _header_row_display;
PNLSettings _last_loaded;
public PNLSettingsVM(PNLSettings settings) {
// init anchor cache, deferrable resync
_anchor_cache = PNLSettings.anchorable_columns.ToDictionary(k => k, k => new AnchorItemVM(this, k));
_sync_anchors = new DeferrableActionWrapper(() => {
for (int i = 0; i < anchor_items.Count; i++) {
anchor_items[i].anchor_index = i + 1;
anchor_items[i].refresh();
}
});
core_settings_vm = new GridSettingsVM();
columns = core_settings_vm.columns.CastObservable<GridColumnSettingsVM, PNLColumnSettingsVM>();
//col_settings => {
// var col_vm = new PNLColumnVM(core_settings_vm, EnumHelper.parse<ColumnKeys>(col_settings.id));
// col_vm.load_from(col_settings);
// return col_vm;
// });
_dispose = Disposable.Create(
anchor_items.Subscribe((s, e) => _sync_anchors.execute()));
load(settings);
}
public IEnumerable<AnchorItemVM> anchor_item_headers => _anchor_cache.Values;
public ObservableCollection<AnchorItemVM> anchor_items { get; } = new ObservableCollection<AnchorItemVM>();
public GridSettingsVM core_settings_vm { get; }
public PNLGroupRowAppearanceVM group_row_defaults { get; } = new PNLGroupRowAppearanceVM();
public PNLCellAppearanceVM header_row_defaults { get; } = new PNLCellAppearanceVM();
public PNLCalculationType calculation_type { get => _calculation_type; set => Set(ref _calculation_type, value); }
public IEnumerable<PNLCalculationType> calculation_types => EnumHelper.get_values_except(PNLCalculationType.None, PNLCalculationType.Same, PNLCalculationType.Opposite);
public PNLHeaderRowDisplay header_row_display { get => _header_row_display; set => Set(ref _header_row_display, value); }
public IEnumerable<PNLHeaderRowDisplay> header_row_display_options => PNLSettings.header_row_display_options;
public IReadOnlyObservableCollection<PNLColumnSettingsVM> columns { get; }
string ITemplateSelectorKey.key => GridSettingsTemplateKeys.PNL;
public ObservableCollection<NamedVM<GridColumnSettingsVM>> cell_types { get; }
public PNLSettings get_result() {
var s = _last_loaded == null ? new PNLSettings() : SparkUtils.clone_proto(_last_loaded);
s.calculation_type = _calculation_type;
s.grid = core_settings_vm.get_result();
// move anchor cols to the left, order wrt grouping hierarchy
var new_cols = new Lazy<List<GridColumnSettings>>(() => s.grid.columns.ToList());
for (int i = 0; i < s.grid.columns.Count; i++) {
@@ -75,146 +83,147 @@ namespace bts.shell.pnl {
new_cols.Value.Insert(anchor_index, s.grid.columns[i]);
}
}
}
// rule-based animations
var trigger_anims = core_settings_vm.columns
.OfType<PNLColumnVM>()
.SelectMany(c => c.get_triggers())
.GroupBy(tup => tup.expression)
.Where(g => CoreExpressionEvaluator<PNLRowData>.validate(g.Key))
.Select(g => (
expression: g.Key,
animations: g.ToArray()))
.ToList();
var normalized_anims = trigger_anims
.Where(tup => CoreExpressionEvaluator<PNLNormalizedData>.validate(tup.expression))
.ToList();
s = s.with(anchors: anchor_items.Select(vm => vm.key),
grid: new_cols.IsValueCreated ? s.grid.with(columns: new_cols.Value) : null,
group_columns: core_settings_vm.columns.OfType<PNLColumnVM>().Select(c => {
var col_settings = c.create();
col_settings = c.group_column_vm.apply_to(col_settings);
return col_settings;
}),
header_columns: core_settings_vm.columns.OfType<PNLColumnVM>().Select(c => {
var col_settings = c.create();
col_settings = c.header_column_vm.apply_to(col_settings);
return col_settings;
}),
animations: trigger_anims.Select(
ta => new PNLAnimationTriggerData {
expression = ta.expression,
animations = Array.ConvertAll(ta.animations, t => t.row)
}),
group_header_animations: normalized_anims
.Select(
ta => new PNLAnimationTriggerData {
expression = ta.expression,
animations = Array.ConvertAll(ta.animations, t => t.group)
}),
header_row_animations: normalized_anims.Select(
ta => new PNLAnimationTriggerData {
expression = ta.expression,
animations = Array.ConvertAll(ta.animations, t => t.header)
}));
s.header_row_display = header_row_display;
s.header_cell_appearance = PNLCellAppearanceProto.from_appearance(header_row_defaults.to_appearance());
// group
var (group_cell_appearance, group_row_background_colors) = group_row_defaults.create();
s.group_cell_appearance = PNLCellAppearanceProto.from_appearance(group_cell_appearance);
s.group_background_colors.SetItems(group_row_background_colors.Select(c => c.rgb_color()));
//
return s;
}
public void load(PNLSettings value) {
_last_loaded = value;
header_row_defaults.load(value.header_cell_appearance.to_appearance());
group_row_defaults.load_from((value.group_cell_appearance.to_appearance(), value.group_background_colors.WrapSelectorRO(c => c.argb_color())));
var column_map = _map_column_data(value.grid.columns, value.animations);
var group_column_map = _map_column_data(value.group_columns, value.group_header_animations);
var header_column_map = _map_column_data(value.header_columns, value.header_row_animations);
core_settings_vm.load_from(value.grid);
for (int i = 0; i < core_settings_vm.columns.Count; i++) {
var col_vm = core_settings_vm.columns[i] as PNLColumnVM ?? throw new InvalidOperationException($"In {nameof(PNLSettingsVM)}, all columns should be of type {typeof(PNLColumnVM)}.");
var key = EnumHelper.parse<ColumnKeys>(col_vm.id);
var (col_settings, trigger_animations) = column_map[key];
var group_data = group_column_map.GetNullable(key);
var header_data = header_column_map.GetNullable(key);
col_vm.load_from(col_settings, trigger_animations, group_data?.settings, group_data?.triggers, header_data?.triggers, header_data?.settings);
}
using (_sync_anchors.defer_execution())
anchor_items.WrapSelector(a => a.key, k => _anchor_cache[k]).sync_with(value.anchors.Intersect(PNLSettings.anchorable_columns));
header_row_display = value.header_row_display;
calculation_type = value.calculation_type;
}
PNLSettings IValueFactory<PNLSettings>.create() => get_result();
void IValueFactory<PNLSettings>.load_from(PNLSettings value) => load(value);
IDisposable IObservable<PNLSettings>.Subscribe(IObserver<PNLSettings> observer) => throw new NotImplementedException();
static Dictionary<ColumnKeys, (GridColumnSettings settings, Dictionary<string, PNLAnimation> triggers)> _map_column_data(IEnumerable<GridColumnSettings> columns, IEnumerable<PNLAnimationTriggerData> triggers) {
var map = columns.ToDictionary(
c => c.strong_id<ColumnKeys>(),
c => (settings: c, triggers: new Dictionary<string, PNLAnimation>()));
foreach (var trig in triggers) {
foreach (var anim in trig.animations) {
if (map.TryGetValue(anim.column, out var col_data))
col_data.triggers[trig.expression] = anim;
}
}
return map;
}
void _append_or_remove_anchor(ColumnKeys key, bool include) {
if (!_anchor_cache.TryGetValue(key, out var vm))
throw new KeyNotFoundException($"Column key '{key}' is not anchorable (expected one of: {string.Join(", ", PNLSettings.anchorable_columns)}).");
if (vm.include == include)
return;
if (include) {
if (anchor_items.Any(a => a.key == key))
throw new InvalidOperationException($"{nameof(anchor_items)} already contains an element with key '{key}.'");
anchor_items.Add(vm);
vm.anchor_index = anchor_items.Count;
} else {
if (!anchor_items.Remove(vm))
throw new InvalidOperationException($"{nameof(anchor_items)} does not contain an element with key '{key}.'");
vm.anchor_index = null;
}
_sync_anchors.execute();
}
public class AnchorItemVM : ViewModelBase, IEquatable<AnchorItemVM> {
readonly PNLSettingsVM _owner;
int? _anchor_index;
public AnchorItemVM(PNLSettingsVM owner, ColumnKeys key) {
_owner = owner;
this.key = key;
}
public ColumnKeys key { get; }
public bool include { get => anchor_index.HasValue; set => _owner._append_or_remove_anchor(key, value); }
public int? anchor_index { get => _anchor_index; set => Set(ref _anchor_index, value); }
public void refresh() =>
RaisePropertyChanged(nameof(include), nameof(anchor_index));
public bool Equals(AnchorItemVM other) =>
other != null && key == other.key;
public override bool Equals(object obj) =>
Equals(obj as AnchorItemVM);
public override int GetHashCode() =>
key.GetHashCode();
public override string ToString() =>
EnumHelper.get_display_name(key);
}
}
}
// rule-based animations
var trigger_anims = columns
.SelectMany(c => c.get_triggers())
.GroupBy(tup => tup.expression)
.Where(g => CoreExpressionEvaluator<PNLRowData>.validate(g.Key))
.Select(g => (
expression: g.Key,
animations: g.ToArray()))
.ToList();
var normalized_anims = trigger_anims
.Where(tup => CoreExpressionEvaluator<PNLNormalizedData>.validate(tup.expression))
.ToList();
s = s.with(anchors: anchor_items.Select(vm => vm.key),
grid: new_cols.IsValueCreated ? s.grid.with(columns: new_cols.Value) : null,
group_columns: columns.Select(c => {
var col_settings = c.create();
col_settings = c.group_column_vm.apply_to(col_settings);
return col_settings;
}),
header_columns: columns.Select(c => {
var col_settings = c.create();
col_settings = c.header_column_vm.apply_to(col_settings);
return col_settings;
}),
animations: trigger_anims.Select(
ta => new PNLAnimationTriggerData {
expression = ta.expression,
animations = Array.ConvertAll(ta.animations, t => t.row)
}),
group_header_animations: normalized_anims
.Select(
ta => new PNLAnimationTriggerData {
expression = ta.expression,
animations = Array.ConvertAll(ta.animations, t => t.group)
}),
header_row_animations: normalized_anims.Select(
ta => new PNLAnimationTriggerData {
expression = ta.expression,
animations = Array.ConvertAll(ta.animations, t => t.header)
}));
s.header_row_display = header_row_display;
s.header_cell_appearance = PNLCellAppearanceProto.from_appearance(header_row_defaults.to_appearance());
// group
var (group_cell_appearance, group_row_background_colors) = group_row_defaults.create();
s.group_cell_appearance = PNLCellAppearanceProto.from_appearance(group_cell_appearance);
s.group_background_colors.SetItems(group_row_background_colors.Select(c => c.rgb_color()));
//
return s;
}
public void load(PNLSettings value) {
_last_loaded = value;
header_row_defaults.load(value.header_cell_appearance.to_appearance());
group_row_defaults.load_from((value.group_cell_appearance.to_appearance(), value.group_background_colors.WrapSelectorRO(c => c.argb_color())));
var column_map = _map_column_data(value.grid.columns, value.animations);
var group_column_map = _map_column_data(value.group_columns, value.group_header_animations);
var header_column_map = _map_column_data(value.header_columns, value.header_row_animations);
core_settings_vm.load(value.grid, false);
using (core_settings_vm.begin_change_columns()) {
core_settings_vm.columns.SetItems(value.grid.columns.Select(c => {
var vm = new PNLColumnSettingsVM();
var key = EnumHelper.parse<ColumnKeys>(c.id);
var (col_settings, trigger_animations) = column_map[key];
var group_data = group_column_map.GetNullable(key);
var header_data = header_column_map.GetNullable(key);
vm.load_from(col_settings, trigger_animations, group_data?.settings, group_data?.triggers, header_data?.triggers, header_data?.settings);
return vm;
}));
}
using (_sync_anchors.defer_execution())
anchor_items.WrapSelector(a => a.key, k => _anchor_cache[k]).sync_with(value.anchors.Intersect(PNLSettings.anchorable_columns));
header_row_display = value.header_row_display;
calculation_type = value.calculation_type;
}
PNLSettings IValueFactory<PNLSettings>.create() => get_result();
void IValueFactory<PNLSettings>.load_from(PNLSettings value) => load(value);
IDisposable IObservable<PNLSettings>.Subscribe(IObserver<PNLSettings> observer) => throw new NotImplementedException();
static Dictionary<ColumnKeys, (GridColumnSettings settings, Dictionary<string, PNLAnimation> triggers)> _map_column_data(IEnumerable<GridColumnSettings> columns, IEnumerable<PNLAnimationTriggerData> triggers) {
var map = columns.ToDictionary(
c => c.strong_id<ColumnKeys>(),
c => (settings: c, triggers: new Dictionary<string, PNLAnimation>()));
foreach (var trig in triggers) {
foreach (var anim in trig.animations) {
if (map.TryGetValue(anim.column, out var col_data))
col_data.triggers[trig.expression] = anim;
}
}
return map;
}
void _append_or_remove_anchor(ColumnKeys key, bool include) {
if (!_anchor_cache.TryGetValue(key, out var vm))
throw new KeyNotFoundException($"Column key '{key}' is not anchorable (expected one of: {string.Join(", ", PNLSettings.anchorable_columns)}).");
if (vm.include == include)
return;
if (include) {
if (anchor_items.Any(a => a.key == key))
throw new InvalidOperationException($"{nameof(anchor_items)} already contains an element with key '{key}.'");
anchor_items.Add(vm);
vm.anchor_index = anchor_items.Count;
} else {
if (!anchor_items.Remove(vm))
throw new InvalidOperationException($"{nameof(anchor_items)} does not contain an element with key '{key}.'");
vm.anchor_index = null;
}
_sync_anchors.execute();
}
public class AnchorItemVM : ViewModelBase, IEquatable<AnchorItemVM> {
readonly PNLSettingsVM _owner;
int? _anchor_index;
public AnchorItemVM(PNLSettingsVM owner, ColumnKeys key) {
_owner = owner;
this.key = key;
}
public ColumnKeys key { get; }
public bool include { get => anchor_index.HasValue; set => _owner._append_or_remove_anchor(key, value); }
public int? anchor_index { get => _anchor_index; set => Set(ref _anchor_index, value); }
public void refresh() =>
RaisePropertyChanged(nameof(include), nameof(anchor_index));
public bool Equals(AnchorItemVM other) =>
other != null && key == other.key;
public override bool Equals(object obj) =>
Equals(obj as AnchorItemVM);
public override int GetHashCode() =>
key.GetHashCode();
public override string ToString() =>
EnumHelper.get_display_name(key);
}
}
}
@@ -291,7 +291,7 @@ namespace bts.shell.pnl {
};
public void load_from(PNLSettings settings) {
selection_highlight = settings.grid.selection_highlight.to_brush();
selection_highlight = settings.grid.selection_highlight.to_argb_brush();
grid_background_color = settings.grid.background;
calculation_type = settings.calculation_type;
if (settings.filters != null) {
@@ -383,7 +383,7 @@ namespace bts.shell.pnl {
// hide implicit cols
state.grid = _hide_cols(state.grid, anchors);
return new FillsVM(
_bus, _orders_model, Observable.Return(state), new WindowSettingsManager<GridSettings>.DisconnectedImpl(state.grid));
_bus, _orders_model, state, new WindowSettingsManager<GridSettings>.DisconnectedImpl(state.grid));
}
public FillsVM mk_fills_vm(PNLRowCollectionViewGroup group, ColumnKeys key) {
@@ -393,7 +393,7 @@ namespace bts.shell.pnl {
// hide implicit cols
state.grid = _hide_cols(state.grid, anchors.WrapSubRange(0, group.level));
return new FillsVM(
_bus, _orders_model, Observable.Return(state), _visible_fills_source(), new WindowSettingsManager<GridSettings>.DisconnectedImpl(state.grid));
_bus, _orders_model, state, _visible_fills_source(), new WindowSettingsManager<GridSettings>.DisconnectedImpl(state.grid));
}
public FillsVM mk_fills_vm(PNLHeaderRowDisplay header, ColumnKeys key) {
@@ -419,7 +419,7 @@ namespace bts.shell.pnl {
break;
}
return new FillsVM(
_bus, _orders_model, Observable.Return(state), src, new WindowSettingsManager<GridSettings>.DisconnectedImpl(state.grid));
_bus, _orders_model, state, src, new WindowSettingsManager<GridSettings>.DisconnectedImpl(state.grid));
}
public OrderBookVM mk_orderbook_vm(PNLHeaderRowDisplay header, ColumnKeys key) {
@@ -0,0 +1,359 @@
<UserControl
x:Class="mbts.ui.spark.PNLSettingsView"
x:ClassModifier="internal"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:shell="clr-namespace:bts.shell"
xmlns:vm="clr-namespace:mbts.ui.viewmodels"
xmlns:sp="clr-namespace:mbts.spark"
xmlns:pnl="clr-namespace:bts.shell.pnl"
xmlns:u="clr-namespace:mbts.ui"
xmlns:c="clr-namespace:mbts.ui.controls"
xmlns:cv="clr-namespace:mbts.ui.converters"
xmlns:usp="clr-namespace:mbts.ui.spark"
xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
xmlns:sys="clr-namespace:System;assembly=mscorlib"
Content="{Binding}"
>
<UserControl.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<usp:GridSettingsResources/>
<ResourceDictionary Source="/view/Controls/TabControls/HorizontalTabControl.xaml"/>
</ResourceDictionary.MergedDictionaries>
<sys:Double x:Key="CellTypesTabWidth">183</sys:Double>
<DataTemplate x:Key="RowCellTypeTemplate" DataType="{x:Type pnl:PNLColumnSettingsVM}">
<TextBlock>Row Cell Appearance</TextBlock>
</DataTemplate>
<DataTemplate x:Key="GroupCellTypeTemplate" DataType="{x:Type pnl:PNLColumnSettingsVM}">
<TextBlock>Group Cell Appearance</TextBlock>
</DataTemplate>
<DataTemplate x:Key="HeaderCellTypeTemplate" DataType="{x:Type pnl:PNLColumnSettingsVM}">
<TextBlock>Header Cell Appearance</TextBlock>
</DataTemplate>
<DataTemplate x:Key="ParametersListTemplate">
<ListBox VerticalAlignment="Top" MinWidth="426" ItemsSource="{Binding parameters}" Style="{StaticResource NakedListBox}">
<ListBox.ItemContainerStyle>
<Style TargetType="ListBoxItem" BasedOn="{StaticResource NakedListBoxItem}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ListBoxItem">
<Border Background="Transparent">
<ContentPresenter HorizontalAlignment="Stretch" VerticalAlignment="Stretch"/>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ListBox.ItemContainerStyle>
</ListBox>
</DataTemplate>
<Style x:Key="ParameterGroupTabItem" TargetType="TabItem" BasedOn="{StaticResource LargeMenuControlBase}">
<Setter Property="Padding" Value="14,10,14,10"/>
<Setter Property="Background" Value="#FFE8E8E8"/>
<Setter Property="Width" Value="{StaticResource CellTypesTabWidth}"/>
<Setter Property="HorizontalContentAlignment" Value="Left"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="TabItem">
<Border
x:Name="Border"
CornerRadius="0"
BorderThickness="{TemplateBinding BorderThickness}"
BorderBrush="{TemplateBinding BorderBrush}"
Background="{TemplateBinding Background}"
Padding="{TemplateBinding Padding}"
Height="{TemplateBinding Height}"
Width="{TemplateBinding Width}"
SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"
>
<u:StateManager.Groups>
<u:StateGroupsManager AttachToMembers="True">
<u:StateDefinitionCollection>
<u:MouseStateDefinition MouseSource="{Binding Group.Target.TemplatedParent, RelativeSource={RelativeSource Self}}"/>
</u:StateDefinitionCollection>
</u:StateGroupsManager>
</u:StateManager.Groups>
<VisualStateManager.VisualStateGroups>
<VisualStateGroup Name="MouseStates">
<VisualState Name="Normal"/>
<VisualState Name="MouseOver" Storyboard="{StaticResource Button.MouseOver.Storyboard}"/>
<VisualState Name="Pressed" Storyboard="{StaticResource Button.Pressed.Storyboard}"/>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
<ContentPresenter
Name="presenter"
Grid.Column="1"
ContentSource="Header"
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
RecognizesAccessKey="True"
/>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<DataTemplate x:Key="ParameterGroupTabHeaderTemplate">
<TextBlock x:Name="text" FontSize="13" Text="{Binding name}"/>
<DataTemplate.Triggers>
<DataTrigger Binding="{Binding IsSelected, RelativeSource={RelativeSource AncestorType=TabItem}}" Value="True">
<Setter TargetName="text" Property="TextBlock.FontWeight" Value="Bold"/>
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>
<Style x:Key="ParameterGroupTabControl" TargetType="TabControl">
<Setter Property="Background" Value="Transparent"/>
<Setter Property="BorderThickness" Value="0"/>
<Setter Property="VerticalAlignment" Value="Stretch"/>
<Setter Property="TabStripPlacement" Value="Left"/>
<Setter Property="ItemContainerStyle" Value="{StaticResource ParameterGroupTabItem}"/>
<Setter Property="ItemTemplate" Value="{StaticResource ParameterGroupTabHeaderTemplate}"/>
<Setter Property="ItemsSource" Value="{Binding parameters}"/>
<Setter Property="ContentTemplate" Value="{StaticResource ParametersListTemplate}"/>
</Style>
<DataTemplate DataType="{x:Type pnl:PNLRulesVM}">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<!--<Rectangle Grid.Row="1" HorizontalAlignment="Left" VerticalAlignment="Stretch" Width="{StaticResource CellTypesTabWidth}" Fill="#FFE8E8E8"/>-->
<c:HorizontalTabControl
Grid.Row="1"
Background="Transparent"
BorderThickness="0,1,0,0"
VerticalAlignment="Stretch"
ItemsSource="{Binding parameters}"
SelectedItem="{Binding selected_cell_mode}"
DisplayMemberPath="name"
>
<TabControl.ItemContainerStyle>
<Style TargetType="TabItem" BasedOn="{StaticResource ParameterGroupTabItem}">
<Setter Property="VerticalAlignment" Value="Bottom"/>
<Setter Property="Background">
<Setter.Value>
<SolidColorBrush Color="Transparent"/>
</Setter.Value>
</Setter>
<Setter Property="Padding" Value="4"/>
<Setter Property="Width" Value="{x:Static sys:Double.NaN}"/>
<Setter Property="BorderThickness" Value="1"/>
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<ItemsControl ItemsSource="{Binding parameters}"/>
</DataTemplate>
</Setter.Value>
</Setter>
<Style.Triggers>
<Trigger Property="IsSelected" Value="True">
<!--<Setter Property="BorderBrush" Value=""/>-->
<Setter Property="BorderThickness" Value="1,1,1,0"/>
<Setter Property="Padding" Value="4,6,4,8"/>
<Setter Property="FontWeight" Value="Bold"/>
</Trigger>
</Style.Triggers>
</Style>
</TabControl.ItemContainerStyle>
</c:HorizontalTabControl>
</Grid>
</DataTemplate>
<DataTemplate DataType="{x:Type pnl:PNLColumnSettingsVM}">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<!--<Rectangle Grid.Row="1" HorizontalAlignment="Left" VerticalAlignment="Stretch" Width="{StaticResource CellTypesTabWidth}" Fill="#FFE8E8E8"/>-->
<!--<TabControl
Grid.Row="1"
ItemsSource="{Binding cell_modes}"
SelectedItem="{Binding selected_cell_mode}"
Style="{StaticResource ParameterGroupTabControl}"
/>-->
<ContentControl Content="{Binding column_layout}" ContentTemplate="{StaticResource HeaderedParameterGroup}"/>
<ContentControl Grid.Row="1" Content="{Binding fonts_and_colors}" ContentTemplate="{StaticResource HeaderedParameterGroup}"/>
</Grid>
</DataTemplate>
<DataTemplate DataType="{x:Type sp:GridColumnSettingsVM}">
<ListBox VerticalAlignment="Top" MinWidth="426" ItemsSource="{Binding parameters}" Style="{StaticResource NakedListBox}">
<ListBox.ItemContainerStyle>
<Style TargetType="ListBoxItem" BasedOn="{StaticResource NakedListBoxItem}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ListBoxItem">
<Border Background="Transparent">
<ContentPresenter HorizontalAlignment="Stretch" VerticalAlignment="Stretch"/>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ListBox.ItemContainerStyle>
</ListBox>
</DataTemplate>
<DataTemplate x:Key="HeaderedCellAppearanceContent" DataType="{x:Type sp:GridColumnSettingsVM}">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="36"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<TextBlock VerticalAlignment="Center" FontSize="14" FontWeight="SemiBold">
Cell Appearance
</TextBlock>
<ListBox Grid.Row="1" VerticalAlignment="Top" ItemsSource="{Binding parameters}" Style="{StaticResource NakedListBox}">
<ListBox.ItemContainerStyle>
<Style TargetType="ListBoxItem" BasedOn="{StaticResource NakedListBoxItem}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ListBoxItem">
<Border Background="Transparent">
<ContentPresenter HorizontalAlignment="Stretch" VerticalAlignment="Stretch"/>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ListBox.ItemContainerStyle>
</ListBox>
</Grid>
</DataTemplate>
</ResourceDictionary>
</UserControl.Resources>
<UserControl.ContentTemplate>
<DataTemplate DataType="{x:Type pnl:PNLSettingsVM}">
<Grid>
<Grid.Margin>
<Thickness Left="{StaticResource HorizontalPush2}" Right="{StaticResource HorizontalPush2}"/>
</Grid.Margin>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<c:ActionMenu
Panel.ZIndex="1"
HorizontalAlignment="Right"
VerticalAlignment="Top"
Margin="0,2,0,0"
ItemsSource="{Binding DataContext.action_menu_items, RelativeSource={RelativeSource AncestorType=usp:ShellSettingsDialog}}"
Style="{StaticResource SparkToolBarItem}"
/>
<TabControl x:Name="Tabs" Background="Transparent" HorizontalAlignment="Stretch" MinHeight="480" MinWidth="600" Margin="0,2,0,0" Padding="6">
<TabControl.ItemContainerStyle>
<Style TargetType="TabItem">
<Setter Property="MinWidth" Value="60"/>
<Setter Property="HorizontalAlignment" Value="Center"/>
</Style>
</TabControl.ItemContainerStyle>
<TabItem Header="Grid">
<ItemsControl x:Name="ParameterItemsControl" Grid.IsSharedSizeScope="True" Margin="8,16,0,0" ItemsSource="{Binding core_settings_vm.parameters}"/>
</TabItem>
<TabItem Header="Grouping">
<GroupBox Grid.Column="3" Grid.RowSpan="3" HorizontalAlignment="Stretch" HorizontalContentAlignment="Stretch">
<GroupBox.Resources>
<u:CursorsEx/>
</GroupBox.Resources>
<GroupBox.Header>
<StackPanel Orientation="Horizontal">
<TextBlock>Grouping</TextBlock>
<c:HelpButton>
<c:HelpButton.Resources>
<Style x:Key="HelpButtonToolTipStyleKey" TargetType="ToolTip">
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<TextBlock>
<Run>Check the group parameters you would like to use.</Run>
<LineBreak/>
<Run FontStyle="Italic">Hint: drag/drop the items below to change the grouping hierarchy.</Run>
</TextBlock>
</DataTemplate>
</Setter.Value>
</Setter>
</Style>
</c:HelpButton.Resources>
</c:HelpButton>
<ItemsControl ItemsSource="{Binding anchor_item_headers}" ItemsPanel="{StaticResource HorizontalItemsPanelTemplate}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<CheckBox Margin="4,0,0,0" Content="{Binding key}" ContentTemplate="{StaticResource EnumDescriptionTemplate}" IsChecked="{Binding include}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
</GroupBox.Header>
<ItemsControl
HorizontalAlignment="Left"
VerticalAlignment="Top"
AllowDrop="True"
ItemsSource="{Binding anchor_items}"
ItemTemplate="{DynamicResource AnchorItemTemplate}"
ItemsPanel="{StaticResource HorizontalItemsPanelTemplate}"
Loaded="_pnl_anchor_items_loaded"
/>
</GroupBox>
</TabItem>
<TabItem Header="Columns">
<Grid Grid.IsSharedSizeScope="True">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<!--<ColumnDefinition Width="{Binding Source={StaticResource HorizontalPush4}}"/>-->
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<c:DragDropListBox
x:Name="ColumnListBox"
DataContext="{Binding core_settings_vm}"
SelectedItem="{Binding current_column}"
SelectItemOnDrop="True"
Style="{DynamicResource ColumnsListBox}"
/>
<!--<ListBox
x:Name="CellTypeListBox"
Grid.Column="1"
Width="165"
ItemsSource="{u:EnumBindingSource {x:Type pnl:CellTypes}}"
SelectedIndex="0"
Style="{StaticResource LargeMenuListBox}"
>
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding StringFormat='{}{0} Cell Appearance'}"/>
</DataTemplate>
</ListBox.ItemTemplate>
<ListBox.Background>
<SolidColorBrush Color="#FFDDDDDD"/>
</ListBox.Background>
<ListBox.ItemContainerStyle>
<Style TargetType="ListBoxItem" BasedOn="{StaticResource LargeMenuListBoxItemBase}">
<Setter Property="Padding" Value="8"/>
<Setter Property="FontSize" Value="13"/>
<Setter Property="FontWeight" Value="{Binding IsSelected, RelativeSource={RelativeSource Self}, Converter={StaticResource BooleanFontWeightConverter}}"/>
</Style>
</ListBox.ItemContainerStyle>
</ListBox>-->
<GridSplitter Grid.Column="1" HorizontalAlignment="Left" VerticalAlignment="Stretch" Width="5" Background="Transparent"/>
<ScrollViewer Grid.Column="2" VerticalAlignment="Stretch" VerticalScrollBarVisibility="Auto" Content="{Binding core_settings_vm.current_column}">
<!--<MultiBinding Converter="{x:Static usp:PNLSettingsView.ColumnContentConverter}">
<Binding Path="SelectedItem" ElementName="ColumnListBox"/>
<Binding Path="SelectedItem" ElementName="CellTypeListBox"/>
</MultiBinding>-->
<ScrollViewer.MaxHeight>
<Binding Path="ActualHeight" RelativeSource="{RelativeSource AncestorType=usp:PNLSettingsView}">
<Binding.Converter>
<cv:DoubleSubtractionConverter Value="26"/>
</Binding.Converter>
</Binding>
</ScrollViewer.MaxHeight>
</ScrollViewer>
</Grid>
</TabItem>
</TabControl>
</Grid>
</DataTemplate>
</UserControl.ContentTemplate>
</UserControl>
@@ -0,0 +1,70 @@
using bts.shell.pnl;
using mbts.spark;
using mbts.ui.converters;
using mbts.ui.viewmodels;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
namespace mbts.ui.spark {
/// <summary>
/// Interaction logic for PNLSettingsView.xaml
/// </summary>
partial class PNLSettingsView : UserControl {
public static readonly IValueConverter
DisplayNameVisualStateConverter = ValueConverter.create<bool, string>(can_override => can_override ? VisualStateNames.DisplayName.Active : VisualStateNames.DisplayName.Inactive),
ColumnVisibilityToggleVisualStateConverter = ValueConverter.create<bool, string>(can_override => can_override ? VisualStateNames.ColumnVisibilityToggle.Active : VisualStateNames.ColumnVisibilityToggle.Inactive);
public static IMultiValueConverter
ColumnContentConverter => ValueConverter.create<PNLColumnSettingsVM, CellTypes, GridColumnSettingsVM>((col, cell) => {
if (col != null) {
switch (cell) {
case CellTypes.Row: return col;
case CellTypes.Group: return col.group_column_vm;
case CellTypes.Header: return col.header_column_vm;
}
}
return null;
});
public PNLSettingsView() =>
InitializeComponent();
private void _pnl_anchor_items_loaded(object sender, System.Windows.RoutedEventArgs e) {
}
public static class VisualStateNames {
public static class DisplayName {
public const string GroupName = nameof(DisplayName);
public static readonly string
Active = $"{GroupName}{VisualStates.Active}",
Inactive = $"{GroupName}{VisualStates.Inactive}";
}
public static class ColumnVisibilityToggle {
public const string GroupName = nameof(ColumnVisibilityToggle);
public static readonly string
Active = $"{GroupName}{VisualStates.Active}",
Inactive = $"{GroupName}{VisualStates.Inactive}";
}
}
}
class PNLModalColumnSettingsVM {
public PNLColumnSettingsVM column { get; }
}
class PNLParameterContentTemplateSelector : DataTemplateSelector {
public DataTemplate ParameterList { get; set; }
public DataTemplate TabGroups { get; set; }
public override DataTemplate SelectTemplate(object item, DependencyObject container) {
if (item is ParameterGroupVM g && g.enable_tab_display)
return TabGroups;
return ParameterList;
}
}
}
@@ -0,0 +1,74 @@
using bts.api;
using bts.api.orderman;
using bts.shell;
using bts.utils;
using ProtoBuf;
using System;
using System.ComponentModel;
namespace mbts {
[ProtoContract]
class OrderSentEventPredicate : IEquatable<OrderSentEventPredicate> {
public enum PredicateTypeOneofCase {
[Description("(any order)")]
AnyOrder,
[Description("choose securities")]
SecurityOrder
}
[ProtoMember(1)]
public SecurityPredicate security { get; set; }
public PredicateTypeOneofCase predicate_type_case {
get {
if (security != null)
return PredicateTypeOneofCase.SecurityOrder;
return PredicateTypeOneofCase.AnyOrder;
}
}
public IPredicate<OrderSecurity> create(object obj) =>
create_predicate((INotificationServiceDataCache)obj);
public IPredicate<OrderSecurity> create_predicate(INotificationServiceDataCache data_cache) =>
OrderEventPredicateHelper.create_status_transition_predicate(OrderStatus.Working, security, data_cache);
public INotificationPredicate<OrderSecurity> create_toast(INotificationServiceDataCache data_cache) =>
new MyToaster(security?.create(), data_cache);
public bool Equals(OrderSentEventPredicate other) => other != null && other.security.safe_equals(other.security);
public override bool Equals(object obj) => Equals(obj as OrderSentEventPredicate);
public override int GetHashCode() => security == null ? 0 : security.GetHashCode();
static bool _is_canceled(OrderRecordImmutable o) =>
o.HasState && o.State.Status == OrderStatus.Working;
class MyToaster : INotificationPredicate<OrderSecurity>, IEquatable<MyToaster> {
readonly IPredicate<SecurityImmutable> _security_predicate;
readonly INotificationServiceDataCache _data_cache;
public MyToaster(IPredicate<SecurityImmutable> security_predicate, INotificationServiceDataCache data_cache) {
_security_predicate = security_predicate;
_data_cache = data_cache;
}
public IToastNotification trigger(OrderSecurity order) =>
OrderEventPredicateHelper.check_order_transition_trigger(order, _is_canceled, _security_predicate, _data_cache, out var oldval) ?
ToastNotifications.order_status_changed(order.order.Key,
OrderEventPredicateHelper.get_nullable_status(oldval), OrderStatus.Working, order.security) :
ToastNotifications.empty;
bool IPredicate<OrderSecurity>.trigger(OrderSecurity order) =>
OrderEventPredicateHelper.check_order_transition_predicate(order, _is_canceled, _security_predicate, _data_cache);
public bool Equals(MyToaster other) =>
other != null && EqualityComparer.null_check_equals(_security_predicate, other._security_predicate);
public override bool Equals(object obj) =>
Equals(obj as MyToaster);
public override int GetHashCode() =>
EqualityComparer.aggregate_hash((int)OrderStatus.Working, _security_predicate == null ? 0 : _security_predicate.GetHashCode());
}
}
}
@@ -1,65 +1,15 @@
using bts.api;
using bts.api.orderman;
using bts.api.orderman;
using bts.shell;
using bts.utils;
using mbts.spark;
using mbts.ui;
using mbts.ui.settings;
using mbts.ui.utility;
using mbts.utils;
using ProtoBuf;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reactive.Linq;
using OrderType = bts.api.orderman.CustomOrderDefinition.CustomOneofCase;
using System.ComponentModel;
using PTOC = mbts.OrderRecordTuplePredicate.PredicateTypeOneofCase;
namespace mbts {
static class OrderRecordTuplePredicateVMFactory {
// TODO: uncomment the remaining cases when ready
public static Dictionary<PTOC, ITemplatedPropertyWrapperVM<OrderRecordTuplePredicate>> get_oneofs(IBus bus, OrdersModel orders_model) {
return new Dictionary<PTOC, ITemplatedPropertyWrapperVM<OrderRecordTuplePredicate>> {
{ PTOC.FilledEvent, wrap(rp => rp.filled_event, (rp, p) => rp.filled_event = p, p => p.predicate_type_case, OrderFilledEventPredicateVMFactory.get_oneofs(bus)) },
{ PTOC.CompletedEvent, wrap(rp => rp.completed_event, (rp, p) => rp.completed_event = p, p => p.predicate_type_case, OrderCompletedEventPredicateVMFactory.get_oneofs(bus)) },
{ PTOC.CanceledEvent, wrap(rp => rp.canceled_event, (rp, p) => rp.canceled_event = p, p => p.predicate_type_case, OrderCanceledEventPredicateVMFactory.get_oneofs(bus)) },
//{ PTOC.Security, wrap(rp => rp.security, (rp, p) => rp.security = p, p => p.predicate_type_case, SecurityCriteriaVMFactory.get_oneofs(bus)) },
//{ PTOC.Time, wrap(rp => rp.time_created, (rp, p) => rp.time_created = p, t => t.predicate_type_case, TimePredicateVMFactory.get_oneofs()) },
//{ PTOC.LastChange, wrap(rp => rp.last_change, (rp, p) => rp.last_change = p, t => t.predicate_type_case, TimePredicateVMFactory.get_oneofs()) },
//{ PTOC.User, wrap(rp => rp.user, (rp, p) => rp.user = p, p => p.predicate_type_case, DiscreteValuesPredicateVMFactory.get_oneofs<uint, uint, uint?, uint[], DiscreteUIntPredicate>(
// RXCollection.Create(orders_model.factory.subscribe_user_ids().Select(tups => tups.Select(convert_name_id))), id => id, id => id.Value, id => id, ids => ids, ids => ids.ToArray())) },
//{ PTOC.Account, wrap(rp => rp.account, (rp, p) => rp.account = p, p => p.predicate_type_case, DiscreteValuesPredicateVMFactory.get_oneofs<uint, uint, uint?, uint[], DiscreteUIntPredicate>(
// RXCollection.Create(orders_model.factory.subscribe_account_ids().Select(tups => tups.Select(convert_name_id))), id => id, id => id.Value, id => id, ids => ids, ids => ids.ToArray())) },
//{ PTOC.Exchange, wrap(rp => rp.exchange, (rp, p) => rp.exchange = p, p => p.predicate_type_case, DiscreteValuesPredicateVMFactory.get_oneofs_value_type<Venue, ExchangePredicate>(EnumHelper.get_values_except(Venue.Unset))) },
//{ PTOC.TimeInForce, wrap(rp => rp.time_in_force, (rp, p) => rp.time_in_force = p, p => p.predicate_type_case, DiscreteValuesPredicateVMFactory.get_oneofs_value_type<TimeInForce, TimeInForcePredicate>(EnumHelper.get_values_except(TimeInForce.Undefined))) },
//{ PTOC.Side, wrap(rp => rp.side, (rp, p) => rp.side = p, p => p.predicate_type_case, SidePredicateVMFactory.get_oneofs()) },
//{ PTOC.Price, wrap(rp => rp.price, (rp, p) => rp.price = p, p => p.predicate_type_case, DoublePredicateVMFactory.get_oneofs()) },
//{ PTOC.Size, wrap(rp => rp.quantity, (rp, p) => rp.quantity = p, p => p.predicate_type_case, UIntPredicateVMFactory.get_oneofs()) },
//{ PTOC.Filled, wrap(rp => rp.filled_quantity, (rp, p) => rp.filled_quantity = p, p => p.predicate_type_case, UIntPredicateVMFactory.get_oneofs()) },
//{ PTOC.AvgFilled, wrap(rp => rp.avg_price, (rp, p) => rp.avg_price = p, p => p.predicate_type_case, DoublePredicateVMFactory.get_oneofs()) },
//{ PTOC.LastPrice, wrap(rp => rp.last_price, (rp, p) => rp.last_price = p, p => p.predicate_type_case, DoublePredicateVMFactory.get_oneofs()) },
//{ PTOC.OrderType, wrap(rp => rp.order_type, (rp, p) => rp.order_type = p, p => p.predicate_type_case, DiscreteValuesPredicateVMFactory.get_oneofs_value_type<OrderType, OrderTypePredicate>(EnumHelper.get_values_except(OrderType.None))) },
//{ PTOC.SubType, wrap(rp => rp.order_sub_type, (rp, p) => rp.order_sub_type = p, p => p.predicate_type_case, DiscreteValuesPredicateVMFactory.get_oneofs_value_type<OrderSubType, OrderSubTypePredicate>(EnumHelper.get_values<OrderSubType>())) },
//{ PTOC.Status, wrap(rp => rp.order_status, (rp, p) => rp.order_status = p, p => p.predicate_type_case, DiscreteValuesPredicateVMFactory.get_oneofs_value_type<OrderStatus, OrderStatusPredicate>(EnumHelper.get_values<OrderStatus>())) },
//{ PTOC.LastMessage, wrap(rp => rp.last_message, (rp, p) => rp.last_message = p, p => p.predicate_type_case, StringPredicateVMFactory.get_oneofs()) },
//{ PTOC.SecurityKey, wrap(rp => rp.security_key, (rp, p) => rp.security_key = p, p => p.predicate_type_case, UIntPredicateVMFactory.get_oneofs()) },
//{ PTOC.OrderKey, wrap(rp => rp.order_key, (rp, p) => rp.order_key = p, p => p.predicate_type_case, UIntPredicateVMFactory.get_oneofs()) },
//{ PTOC.ParentKey, wrap(rp => rp.parent_key, (rp, p) => rp.parent_key = p, p => p.predicate_type_case, UIntPredicateVMFactory.get_oneofs()) },
};
//IViewValue<uint> convert_name_id((string name, uint id) tup) =>
// new ViewValueTuple<uint>(tup.id, tup.name);
ITemplatedPropertyWrapperVM<OrderRecordTuplePredicate> wrap<TChildPredicate, TChildPredicateOneof>(
Func<OrderRecordTuplePredicate, TChildPredicate> get,
Action<OrderRecordTuplePredicate, TChildPredicate> set,
Func<TChildPredicate, TChildPredicateOneof> key,
Dictionary<TChildPredicateOneof, ITemplatedPropertyWrapperVM<TChildPredicate>> oneofs) where TChildPredicateOneof : struct where TChildPredicate : new() =>
//
PredicateVMWrapper.wrap_child_oneof<OrderRecordTuplePredicate, TChildPredicate, TChildPredicateOneof>(get, set, key, oneofs);
}
}
[ProtoContract]
class OrderRecordTuplePredicate : IEquatable<OrderRecordTuplePredicate> {
SecurityPredicate _security;
@@ -77,6 +27,7 @@ namespace mbts {
OrderFilledEventPredicate _filled_event;
OrderCompletedEventPredicate _completed_event;
OrderCanceledEventPredicate _canceled_event;
OrderSentEventPredicate _sent_event;
public enum PredicateTypeOneofCase {
None,
@@ -120,12 +71,14 @@ namespace mbts {
OrderKey,
[PredicateCase(is_toastable:false)]
ParentKey,
[PredicateCase("When order gets a fill", PredicateCategory.Event)]
[PredicateCase("Order Filled", PredicateCategory.Event)]
FilledEvent,
[PredicateCase("When order is completed", PredicateCategory.Event)]
[PredicateCase("Order Completed", PredicateCategory.Event)]
CompletedEvent,
[PredicateCase("When order is canceled", PredicateCategory.Event)]
[PredicateCase("Order Canceled", PredicateCategory.Event)]
CanceledEvent,
[PredicateCase("Order Sent", PredicateCategory.Event)]
SentEvent,
[PredicateCase(is_toastable:false)]
AttachedTargetOrderKey
}
@@ -176,6 +129,8 @@ namespace mbts {
public OrderCompletedEventPredicate completed_event { get => _completed_event; set => _set(ref _completed_event, value); }
[ProtoMember(23)]
public OrderCanceledEventPredicate canceled_event { get => _canceled_event; set => _set(ref _canceled_event, value); }
[ProtoMember(26)]
public OrderSentEventPredicate sent_event { get => _sent_event; set => _set(ref _sent_event, value); }
[ProtoMember(24)]
public bool is_toast_enabled { get; set; }
[ProtoMember(25)]
@@ -206,6 +161,7 @@ namespace mbts {
if (_filled_event != null) return PTOC.FilledEvent;
if (_completed_event != null) return PTOC.CompletedEvent;
if (_canceled_event != null) return PTOC.CanceledEvent;
if (_sent_event != null) return PTOC.SentEvent;
if (_attached_target_order_key != null) return PTOC.AttachedTargetOrderKey;
return PTOC.None;
}
@@ -236,6 +192,7 @@ namespace mbts {
case PTOC.FilledEvent: return _filled_event.create_predicate(data_cache);
case PTOC.CompletedEvent: return _completed_event.create_predicate(data_cache);
case PTOC.CanceledEvent: return _canceled_event.create(data_cache);
case PTOC.SentEvent: return _sent_event.create(data_cache);
case PTOC.AttachedTargetOrderKey: return wrap(_attached_target_order_key.create(), r => r.order.attached_target_order_key());
default: return null;
}
@@ -285,6 +242,7 @@ namespace mbts {
case PTOC.FilledEvent: return _filled_event.Equals(other._filled_event);
case PTOC.CompletedEvent: return _completed_event.Equals(other._completed_event);
case PTOC.CanceledEvent: return _canceled_event.Equals(other._canceled_event);
case PTOC.SentEvent: return _sent_event.Equals(other._sent_event);
case PTOC.AttachedTargetOrderKey: return _attached_target_order_key.Equals(other._attached_target_order_key);
case PTOC.None: return true;
default: throw ExceptionHelper.unexpected_oneof_case(ptoc);
@@ -320,6 +278,7 @@ namespace mbts {
case PTOC.FilledEvent: raw_hash = _filled_event.GetHashCode(); break;
case PTOC.CompletedEvent: raw_hash = _completed_event.GetHashCode(); break;
case PTOC.CanceledEvent: raw_hash = _canceled_event.GetHashCode(); break;
case PTOC.SentEvent: raw_hash = _sent_event.GetHashCode(); break;
case PTOC.AttachedTargetOrderKey: raw_hash = _attached_target_order_key.GetHashCode(); break;
case PTOC.None: raw_hash = 0; break;
default: throw ExceptionHelper.unexpected_oneof_case(ptoc);
@@ -355,6 +314,7 @@ namespace mbts {
_filled_event = null;
_completed_event = null;
_canceled_event = null;
_sent_event = null;
_attached_target_order_key = null;
}
field = value;
@@ -363,6 +323,7 @@ namespace mbts {
[ProtoContract]
class OrderRecordTuplePredicateAnimations : IPredicateAnimationsData<OrderSecurity, OrderRecordTuplePredicate> {
const bool _default_is_enabled = true;
[ProtoMember(1)]
OrderRecordTuplePredicate _predicate;
[ProtoMember(2)]
@@ -371,19 +332,31 @@ namespace mbts {
List<RowAnimation> _row_animations;
[ProtoMember(4)]
List<SoundNotification> _sound_animations;
// is_enabled
[ProtoMember(5)]
[DefaultValue(_default_is_enabled)]
bool _is_enabled;
public OrderRecordTuplePredicateAnimations() { }
public OrderRecordTuplePredicateAnimations() =>
_is_enabled = _default_is_enabled;
public OrderRecordTuplePredicateAnimations(OrderRecordTuplePredicate predicate, IEnumerable<CellAnimation> cell_animations, IEnumerable<RowAnimation> row_animations, IEnumerable<SoundNotification> sound_animations) {
public OrderRecordTuplePredicateAnimations(
OrderRecordTuplePredicate predicate,
IEnumerable<CellAnimation> cell_animations,
IEnumerable<RowAnimation> row_animations,
IEnumerable<SoundNotification> sound_animations,
bool is_enabled = true) {
_predicate = predicate;
_cell_animations = new List<CellAnimation>(cell_animations);
_row_animations = new List<RowAnimation> ( row_animations );
_sound_animations = new List<SoundNotification> ( sound_animations );
_cell_animations = new List<CellAnimation>(cell_animations.empty_if_null());
_row_animations = new List<RowAnimation>(row_animations.empty_if_null());
_sound_animations = new List<SoundNotification>(sound_animations.empty_if_null());
_is_enabled = is_enabled;
}
public OrderRecordTuplePredicate predicate_data => _predicate;
public IReadOnlyList<CellAnimation> cell_animations => _cell_animations ?? (_cell_animations = new List<CellAnimation>());
public IReadOnlyList<RowAnimation> row_animations => _row_animations ?? (_row_animations = new List<RowAnimation>());
public IReadOnlyList<SoundNotification> sound_notifications => _sound_animations ?? (_sound_animations = new List<SoundNotification>());
public bool is_enabled => _is_enabled;
}
}
}
+3 -1
View File
@@ -44,6 +44,8 @@
<ResourceDictionary Source="/Modules/Spreader/ExecutionStrategyEditor.xaml"/>
<ResourceDictionary Source="/view/Controls/Order/AlgoContentTemplateSelector.xaml"/>
<ResourceDictionary Source="/view/Controls/Order/AlgoPickerNodeView.xaml"/>
<ResourceDictionary Source="/view/Controls/Popup/PopupControl.xaml"/>
<ResourceDictionary Source="/view/Controls/ColorPicker/TabularColorPicker.xaml"/>
</ResourceDictionary.MergedDictionaries>
<Style TargetType="ScrollViewer">
@@ -559,4 +561,4 @@
<Setter Property="Command" Value="{Binding command}"/>
<Setter Property="Icon" Value="{Binding icon, Converter={StaticResource IconConverter}}"/>
</Style>
</ResourceDictionary>
</ResourceDictionary>
@@ -7,7 +7,7 @@ namespace mbts.ui {
if (style == null) {
if (!allow_null)
throw new ArgumentException(nameof(style));
} else if (style.TargetType != expected)
} else if (!style.TargetType.IsAssignableFrom(expected))
throw WPFExceptionBuilder.style_target_type(expected, style.TargetType);
}
@@ -1,65 +0,0 @@
using System;
namespace mbts.ui.utility.text {
/// <summary>
/// Contains a font family, size, and foreground (uint).
/// <para/> NOTE: does not account for font weight, or typeface.
/// </summary>
struct FontParams : IEquatable<FontParams> {
public readonly FontFamily font_family;
public readonly double font_size;
public readonly uint foreground;
public FontParams(FontFamily font_family, double font_size, uint foreground) {
this.font_family = font_family;
this.font_size = font_size;
this.foreground = foreground;
}
public bool Equals(FontParams other) =>
other.font_family == font_family &&
other.font_size == font_size &&
other.foreground == foreground;
public override bool Equals(object obj) =>
obj is FontParams fsk && Equals(fsk);
public override int GetHashCode() =>
font_family.GetHashCode() ^
font_size.GetHashCode() ^
foreground.GetHashCode();
public static bool operator ==(FontParams x, FontParams y) =>
x.Equals(y);
public static bool operator !=(FontParams x, FontParams y) =>
!x.Equals(y);
}
struct FontParamsKey<T> where T : struct, IEquatable<T> {
public readonly T key;
public readonly FontParams font_params;
public FontParamsKey(T key, FontParams font_params) {
this.key = key;
this.font_params = font_params;
}
public bool Equals(FontParamsKey<T> other) =>
other.key.Equals(key) &&
other.font_params == font_params;
public override bool Equals(object obj) =>
obj is FontParamsKey<T> fpk && Equals(fpk);
public override int GetHashCode() =>
key.GetHashCode() ^
font_params.GetHashCode();
public static bool operator ==(FontParamsKey<T> x, FontParamsKey<T> y) =>
x.Equals(y);
public static bool operator !=(FontParamsKey<T> x, FontParamsKey<T> y) =>
!x.Equals(y);
}
}
@@ -2,6 +2,12 @@
namespace mbts {
public static class DoubleHelper {
public static bool AnyNaN(params double[] values) {
for (int i = 0; i < values.Length; i++)
if (double.IsNaN(values[i]))
return true;
return false;
}
public static bool IsNaNOrInfinity(double d) => double.IsNaN(d) || double.IsInfinity(d);
public static bool IsNaNOrInfinityOr(double d, params double[] values) => IsNaNOrInfinity(d) || values.Contains(d);
public static bool IsOneOf(double d, params double[] values) => values.Contains(d);
@@ -1,4 +1,5 @@
using bts.shell.pnl;
using bts.shell.orderbook;
using bts.shell.pnl;
using bts.utils;
using mbts.ui.settings;
using System;
@@ -15,6 +16,7 @@ namespace bts.settings {
// determine the various windows to which such rules apply.
interface IFillsGridSettingsMigrationRule : ISettingsMigrationRule<GridSettings> { }
interface IMarketViewGridSettingsMigrationRule : ISettingsMigrationRule<GridSettings> { }
interface IOrderBookGridSettingsMigrationRule : ISettingsMigrationRule<OrderBookGridSettings> { }
interface IPAndLGridSettingsMigrationRule : ISettingsMigrationRule<GridSettings> { }
interface IPNLSettingsMigrationRule : ISettingsMigrationRule<PNLSettings> { }
interface ISyntheticManagementMigrationRule : ISettingsMigrationRule<GridSettings> { }
@@ -1,5 +1,6 @@
using bts;
using bts.settings;
using bts.shell.orderbook;
using bts.shell.pnl;
using bts.utils;
using mbts.spark;
@@ -28,6 +29,9 @@ namespace mbts.persist {
_pandl_propi = ImmutableProperty.create<UserSettings, GridSettings>(us => us.pnl_grid, (us, gs) => us.with(provided_pnl_gridsettings: gs)),
_synth_propi = ImmutableProperty.create<UserSettings, GridSettings>(us => us.synthetic_management_grid, (us, gs) => us.with(provided_synthetic_management_gridsettings: gs));
static readonly IImmutableProperty<UserSettings, OrderBookGridSettings>
_orderbook_propi = ImmutableProperty.create<UserSettings, OrderBookGridSettings>(us => us.order_book_grid, (us, gs) => us.with(order_book_grid: gs));
static readonly IImmutableProperty<UserSettings, PNLSettings>
_pnl_propi = ImmutableProperty.create<UserSettings, PNLSettings>(us => us.pnl, (us, pnl) => us.with(pnl: pnl));
@@ -35,6 +39,7 @@ namespace mbts.persist {
readonly BehaviorSubject<UserSettings> _default_settings_subject = new BehaviorSubject<UserSettings>(UserSettings.factory_defaults);
readonly IEnumerable<IFillsGridSettingsMigrationRule> _fills_migration_rules;
readonly IEnumerable<IMarketViewGridSettingsMigrationRule> _mview_migration_rules;
readonly IEnumerable<IOrderBookGridSettingsMigrationRule> _orderbook_migration_rules;
readonly IEnumerable<IPAndLGridSettingsMigrationRule> _pandl_migration_rules;
readonly IEnumerable<IPNLSettingsMigrationRule> _pnl_migration_rules;
readonly IEnumerable<ISyntheticManagementMigrationRule> _synth_migration_rules;
@@ -47,6 +52,7 @@ namespace mbts.persist {
IUserPersist persist,
[ImportMany]IEnumerable<IFillsGridSettingsMigrationRule> fills_migration_rules,
[ImportMany]IEnumerable<IMarketViewGridSettingsMigrationRule> mview_migration_rules,
[ImportMany]IEnumerable<IOrderBookGridSettingsMigrationRule> orderbook_migration_rules,
[ImportMany]IEnumerable<IPAndLGridSettingsMigrationRule> pandl_migration_rules,
[ImportMany]IEnumerable<IPNLSettingsMigrationRule> pnl_migration_rules,
[ImportMany]IEnumerable<ISyntheticManagementMigrationRule> synth_migration_rules) {
@@ -54,6 +60,7 @@ namespace mbts.persist {
_persist = persist;
_fills_migration_rules = fills_migration_rules ?? Enumerable.Empty<IFillsGridSettingsMigrationRule>();
_mview_migration_rules = mview_migration_rules ?? Enumerable.Empty<IMarketViewGridSettingsMigrationRule>();
_orderbook_migration_rules = orderbook_migration_rules ?? Enumerable.Empty<IOrderBookGridSettingsMigrationRule>();
_pandl_migration_rules = pandl_migration_rules ?? Enumerable.Empty<IPAndLGridSettingsMigrationRule>();
_pnl_migration_rules = pnl_migration_rules ?? Enumerable.Empty<IPNLSettingsMigrationRule>();
_synth_migration_rules = synth_migration_rules ?? Enumerable.Empty<ISyntheticManagementMigrationRule>();
@@ -130,6 +137,12 @@ namespace mbts.persist {
_mview_migration_rules,
_mview_propi);
public IWindowSettingsManager<OrderBookGridSettings> create_orderbook_settings_manager(OrderBookGridSettings instance_settings) =>
_create_settings_manager(
instance_settings,
_orderbook_migration_rules,
_orderbook_propi);
public IWindowSettingsManager<GridSettings> create_pandl_settings_manager(GridSettings instance_settings) =>
_create_settings_manager(
instance_settings,
+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,44 @@ namespace mbts.ui.shell {
public delegate void PropertyChange<T>(T oldval, T newval);
static class ValidationViewModelExtensions {
public static IDisposable echo<T>(this IRaiseNotifyPropertyChanged vm, IProperty<T> source_property, string target_property_name) {
source_property.Changed += on_changed;
return Disposable.Create(() => source_property.Changed -= on_changed);
void on_changed(T oldval, T newval) =>
vm.raise_notify_property_changed(target_property_name);
}
//public static IDisposable copy<T>(this IProperty<T> target, IProperty<T> source, string property_name) =>
// target.copy(source, property_name, property_name);
public static IDisposable copy<T>(this IProperty<T> target, IProperty<T> source) {
//var target_prop = target.GetType().GetProperty(target_property_name);
source.Changed += on_changed;
return Disposable.Create(() => source.Changed -= on_changed);
void on_changed(T oldval, T newval) {
target.Value = newval;
}
}
public static void assert(this IValidationViewModel vm, bool condition, string property, string message) {
if (condition) vm.clear_error(property);
else vm.set_error(property, message);
}
public static IDisposable subscribe_changed<T>(this IProperty<T> property, IObserver<(T oldval, T newval)> observer) {
property.Changed += on_changed;
return Disposable.Create(() => property.Changed -= on_changed);
void on_changed(T oldval, T newval) =>
observer.OnNext((oldval, newval));
}
public static IDisposable subscribe_changing<T>(this IProperty<T> property, IObserver<(T oldval, T newval)> observer) {
property.Changing += on_changing;
return Disposable.Create(() => property.Changing -= on_changing);
void on_changing(T oldval, T newval) =>
observer.OnNext((oldval, newval));
}
public static IObservable<bool> subscribe_has_errors(this INotifyDataErrorInfo vm) =>
Observable.Create<bool>(o => {
vm.ErrorsChanged += on_errors_changed;
@@ -53,6 +91,8 @@ namespace mbts.ui.shell {
// ensure notifications will be in on the same thread as IProperty<T>.Subscribe (don't use .Select extension)
Observable.Create<T>(o => property.Subscribe(ch => o.OnNext(ch.newval)));
//public static IDisposable S
public static IProperty<T> block_reentrancy<T>(this IProperty<T> source) =>
new GatedPropertyWrapper<T>(source);
@@ -89,6 +129,7 @@ namespace mbts.ui.shell {
return GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance);
});
}
protected void RaisePropertyChanged([CallerMemberName]string property="") {
if (PropertyChanged != null) {
PropertyChanged(this, new PropertyChangedEventArgs(property));
@@ -22,12 +22,20 @@ namespace bts.proto {
}
public Color color => _color.rgb_color();
public Color argb_color => Color.FromArgb((byte)(MathUtils.within_range(0, 1, _opacity) * 255.0), color.R, color.G, color.B);
// TODO: get rid of this nonsense, all we need is a uint to store color & opacity
// (opacity is just the alpha bits of the color)
public Color argb_color =>
opacity == 1 ?
_color.argb_color() :
Color.FromArgb((byte)(MathUtils.within_range(0, 1, _opacity) * 255.0), color.R, color.G, color.B);
public double opacity => _opacity;
public ColorOpacityProto with(uint? color = null, double? opacity = null) =>
new ColorOpacityProto(color ?? _color, opacity ?? _opacity);
public SolidColorBrush to_argb_brush() =>
new SolidColorBrush(argb_color) { Opacity = _opacity };
public SolidColorBrush to_brush() =>
new SolidColorBrush(_color.rgb_color()) { Opacity = _opacity };
+10
View File
@@ -5,9 +5,19 @@
xmlns:ui="clr-namespace:mbts.ui"
>
<!--Control Base Colors-->
<Color x:Key="Control.LightGrayColor">#E5E5E5</Color>
<Color x:Key="Control.LightMediumGrayColor">#CFCFCF</Color>
<Color x:Key="Control.MediumGrayColor">#B3B3B3</Color>
<Color x:Key="Control.AlphaLightGrayColor">#10000000</Color>
<Color x:Key="Control.AlphaLightMediumGrayColor">#24000000</Color>
<Color x:Key="Control.AlphaMediumGrayColor">#3A000000</Color>
<Color x:Key="Control.AlphaMediumGrayColor2">#FF0000</Color>
<Color x:Key="Text.Prompt.Foreground.Color">#FF999999</Color>
<SolidColorBrush x:Key="Control.AlphaLightGrayBrush" Color="{StaticResource Control.AlphaLightGrayColor}"/>
<SolidColorBrush x:Key="Control.AlphaLightMediumGrayBrush" Color="{StaticResource Control.AlphaLightMediumGrayColor}"/>
<SolidColorBrush x:Key="Control.LightGrayBrush" Color="{StaticResource Control.LightGrayColor}"/>
<SolidColorBrush x:Key="Control.LightMediumGrayBrush" Color="{StaticResource Control.LightMediumGrayColor}"/>
<SolidColorBrush x:Key="Control.MediumGrayBrush" Color="{StaticResource Control.MediumGrayColor}"/>
<SolidColorBrush x:Key="CommonBackground" Color="{ui:BTSColor common_background}" />
<SolidColorBrush x:Key="ControlBackground" Color="{x:Static mbts:BTSColors.common_background}" />
@@ -9,13 +9,14 @@
<SolidColorBrush x:Key="Ellipse.Pressed.Brush" Color="#FF3C77DD"/>
<SolidColorBrush x:Key="Ellipse.Disabled.Brush" Color="Gray"/>
<Style TargetType="c:ExpanderToggleButton" BasedOn="{StaticResource {x:Type ToggleButton}}">
<Setter Property="Background" Value="Transparent"/>
<Setter Property="BorderBrush" Value="Black"/>
<Setter Property="BorderThickness" Value="1"/>
<Setter Property="Padding" Value="3,0,0,0"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ToggleButton">
<Border>
<Border Background="{TemplateBinding Background}">
<Grid Background="Transparent">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
@@ -1,11 +1,19 @@
using System.Windows;
using System.Windows.Controls.Primitives;
using System.Windows.Input;
namespace mbts.ui.controls {
/// <summary>
/// Interaction logic for ExpanderToggleButton.xaml
/// </summary>
class ExpanderToggleButton : ToggleButton {
public static readonly RoutedCommand ToggleCommand = new RoutedCommand();
public static readonly ExecutedRoutedEventHandler ToggleExecuted = (s, e) => {
var bn = s as ToggleButton ?? e.Parameter as ToggleButton;
if (bn != null)
bn.SetCurrentValue(IsCheckedProperty, !bn.IsChecked.GetValueOrDefault());
};
static ExpanderToggleButton() =>
DefaultStyleKeyProperty.OverrideMetadata(typeof(ExpanderToggleButton), new FrameworkPropertyMetadata(typeof(ExpanderToggleButton)));
}
@@ -1,4 +1,5 @@
using mbts.spark;
using bts.collections;
using mbts.spark;
using mbts.spark.utilities;
using ProtoBuf;
using System.Collections.Generic;
@@ -23,6 +24,7 @@ namespace mbts.ui.controls.color_picker {
yield return factory_order_colors_palette(order_factory);
yield return application_palette();
yield return bts_palette();
yield return system_palette();
yield return blank_palette();
}
@@ -109,6 +111,14 @@ namespace mbts.ui.controls.color_picker {
}
};
public static ColorPalette system_palette() =>
new ColorPalette {
group = "System",
colors = ColorUtils.SystemColors.WrapReadOnlySelector(
nc => new NamedColor(nc.value, nc.name))
.ToList()
};
public static ColorPalette blank_palette() =>
new ColorPalette {
group = "Custom",
@@ -1,11 +1,132 @@
using System;
using bts.collections;
using bts.utils;
using mbts.ui.utilities;
using mbts.utils;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Runtime.InteropServices;
using Color = System.Windows.Media.Color;
using System.Windows.Media;
namespace mbts.ui.controls.color_picker {
public static class ColorUtils {
static class ColorUtils {
// (L)ight -> (H)ue comparer (hierarchical)
public static readonly IComparer<Color> LHComparer =
new ComparerBuilder<Color> {
ReverseComparer<double>.Default.wrap<double, Color>(brightness),
Comparer<double>.Default.wrap<double, Color>(hue)
}.to_comparer();
static readonly Named<Color>[]
_system_colors,
// 'pure' means no grays
_pure_web_colors,
_gray_web_colors,
_all_web_colors,
/// The 'grid' arrays below have the same colors as the arrays above, but are sorted differently to represent a grid structure:
/// - 140 web colors are split into 7 rows (20 items each),
/// - each row represents a 'hue' bucket
/// - the items within each row are sorted by lightness
/// Note: these 'grid-sorted' arrays serve as the [default]ItemsSource for the <see cref="TabularColorPicker"/>, which presents the items in a UniformGrid.
_pure_web_colors_grid,
_all_web_colors_grid;
static ColorUtils() {
var named_color_lh_comp = LHComparer.wrap<Color, Named<Color>>(nc => nc.value);
_system_colors = Array.ConvertAll(
typeof(Colors).GetProperties(BindingFlags.Static | BindingFlags.Public),
p => new Named<Color>(
DisplayNameHelper.camel_to_title_case(p.Name),
(Color)p.GetValue(null)));
Array.Sort(_system_colors, named_color_lh_comp);
_all_web_colors = Array.ConvertAll(
typeof(System.Drawing.Color)
.GetProperties(BindingFlags.Public | BindingFlags.Static),
p => {
var c = System.Drawing.Color.FromName(p.Name);
return new Named<Color>(p.Name, Color.FromArgb(c.A, c.R, c.G, c.B));
});
Array.Sort(_all_web_colors, named_color_lh_comp);
var all_web_colors_grid = new Named<Color>[_all_web_colors.Length - 1];
int adj = 0;
for (int i = 0; i < _all_web_colors.Length; i++) {
if (_all_web_colors[i].value.A == 0) {
adj++;
continue;
}
all_web_colors_grid[i - adj] = _all_web_colors[i];
}
SortColorGrid(ref all_web_colors_grid, nc => nc.value);
_all_web_colors_grid = all_web_colors_grid;
// break up into pure/gray
int num_gray = all_web_colors_grid.Count(nc => nc.value.is_gray());
_pure_web_colors = new Named<Color>[all_web_colors_grid.Length - num_gray];
_gray_web_colors = new Named<Color>[num_gray];
int gpos = 0;
int egpos = 0;
for (int i = 0; i < all_web_colors_grid.Length; i++) {
var nc = all_web_colors_grid[i];
if (nc.value.is_gray()) {
_gray_web_colors[gpos++] = nc;
} else {
_pure_web_colors[egpos++] = nc;
}
}
// gray
Array.Sort(_gray_web_colors, Comparer<double>.Default.wrap<double, Named<Color>>(nc => nc.value.brightness()));
// pure
var pure_web_colors_grid = _pure_web_colors.ToArray();
SortColorGrid(ref pure_web_colors_grid, nc => nc.value);
_pure_web_colors_grid = pure_web_colors_grid;
}
public static IReadOnlyList<Named<Color>> SystemColors =>
_system_colors;
public static IReadOnlyList<NamedColorVM> SystemColorVMs =>
_system_colors.WrapSelectorRO(_mk_readonly_vm);
public static IReadOnlyList<Named<Color>> WebColors =>
_all_web_colors;
public static IReadOnlyList<NamedColorVM> WebColorVMs =>
_all_web_colors.WrapSelectorRO(_mk_readonly_vm);
public static IReadOnlyList<NamedColorVM> GrayWebColorVMs =>
_gray_web_colors.WrapSelectorRO(_mk_readonly_vm);
public static IReadOnlyList<NamedColorVM> PureWebColorVMs =>
_pure_web_colors_grid.WrapSelectorRO(_mk_readonly_vm);
public static IReadOnlyList<NamedColorVM> WebColorVMGrid =>
_all_web_colors_grid.WrapSelectorRO(_mk_readonly_vm);
public static void SortColorGrid<T>(ref T[] color_items, Func<T, Color> get_color, int column_count = 20) {
// Sorting colors is a lot harder than one would think, esp when the distribution of rgb values is non-linear.
// The questions one must ask are "how many shade groups should there be, and futhermore, how is a shade defined?"
// The logic employed below first breaks the colors up into 6 groups of hues (each occupying a range of 60/360 degrees of hue).
// A somewhat aribitrary offset of '10' is applied to achieve 'better looking' hue groups.
// -------------------------------------------------------------------
/// 1) sort by hue buckets (divide the 360 degrees of hue into 6 slices)
var hue_comp = Comparer<int>.Default.wrap<int, T>(ci => {
double raw_hue = get_color(ci).hue();
double adj_hue = raw_hue + 10;
if (adj_hue > 360) {
adj_hue -= 360;
}
int hue_bucket = (int)Math.Ceiling(adj_hue / 60.0);
return hue_bucket;
});
Array.Sort(color_items, hue_comp);
/// 2) break up the array into rows, and sort each row by lightness
var lightness_comp = Comparer<double>.Default.wrap<double, T>(ci => get_color(ci).brightness()).reverse();
for (int i = 0; i < color_items.Length; i+=column_count) {
int row_size = Math.Min(column_count, color_items.Length - i);
Array.Sort(color_items, i, row_size, lightness_comp);
}
}
[DllImport("shlwapi.dll")]
public static extern int ColorHLSToRGB(int H, int L, int S);
@@ -15,7 +136,6 @@ namespace mbts.ui.controls.color_picker {
return Color.FromArgb(255, bytes[0], bytes[1], bytes[2]);
}
public static string ToHexString(this Color c) =>
"#" + c.R.ToString("X2") + c.G.ToString("X2") + c.B.ToString("X2");
@@ -170,17 +290,7 @@ namespace mbts.ui.controls.color_picker {
return Color.FromRgb((byte)(from.R + rdiff), (byte)(from.G + gdiff), (byte)(from.B + bdiff));
}
// https://stackoverflow.com/questions/5091455/web-color-list-in-c-sharp-application
public static List<Color> GetWebColors() {
Type colors = typeof(System.Drawing.Color);
PropertyInfo[] colorInfo = colors.GetProperties(BindingFlags.Public |
BindingFlags.Static);
List<Color> list = new List<Color>();
foreach (PropertyInfo info in colorInfo) {
var c = System.Drawing.Color.FromName(info.Name);
list.Add(Color.FromArgb(c.A, c.R, c.G, c.B));
}
return list;
}
static NamedColorVM _mk_readonly_vm(Named<Color> nc) =>
new NamedColorVM(new NamedColor(nc.value, nc.name), false);
}
}
@@ -5,6 +5,7 @@
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:u="clr-namespace:mbts.ui"
xmlns:c="clr-namespace:mbts.ui.controls"
xmlns:cv="clr-namespace:mbts.ui.converters"
xmlns:cp="clr-namespace:mbts.ui.controls.color_picker"
>
<Control.Resources>
@@ -74,7 +75,29 @@
</Control.Resources>
<Control.Template>
<ControlTemplate TargetType="cp:ColorPickerControl">
<Grid>
<Grid DataContext="{Binding Model, RelativeSource={RelativeSource TemplatedParent}}">
<u:StateManager.VisualState>
<Binding Path="ShowControls" RelativeSource="{RelativeSource TemplatedParent}">
<Binding.Converter>
<cv:BooleanToObjectConverter ValueForFalse="HideControls" ValueForTrue="ShowControls"/>
</Binding.Converter>
</Binding>
</u:StateManager.VisualState>
<VisualStateManager.VisualStateGroups>
<VisualStateGroup Name="ControlStates">
<VisualState Name="ShowControls"/>
<VisualState Name="HideControls">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="PaletteTabsGrid" Storyboard.TargetProperty="Visibility">
<DiscreteObjectKeyFrame KeyTime="0" Value="{x:Static Visibility.Collapsed}"/>
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="SlidersGrid" Storyboard.TargetProperty="Visibility">
<DiscreteObjectKeyFrame KeyTime="0" Value="{x:Static Visibility.Collapsed}"/>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="326" />
<ColumnDefinition Width="*" />
@@ -188,7 +211,7 @@
</TabItem>
</TabControl>
</Grid>
<Grid Grid.Column="1" Margin="{StaticResource PanelMargin}" HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
<Grid x:Name="SlidersGrid" Grid.Column="1" Margin="{StaticResource PanelMargin}" HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="15" />
<ColumnDefinition Width="*" MinWidth="120" />
@@ -299,7 +322,7 @@
/>
</Grid>
<Grid Grid.Row="1" Grid.ColumnSpan="3" Margin="3">
<Grid x:Name="PaletteTabsGrid" Grid.Row="1" Grid.ColumnSpan="3" Margin="3">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*"/>
@@ -311,7 +334,12 @@
BorderThickness="2"
Background="{Binding result, Converter={StaticResource ColorToBrushConverter}}"
/>
<TabControl ItemsSource="{Binding palettes}" Grid.Column="1" HorizontalAlignment="Stretch" ItemContainerStyle="{StaticResource PaletteTabItem}"/>
<TabControl
ItemsSource="{Binding palettes}"
Grid.Column="1"
HorizontalAlignment="Stretch"
ItemContainerStyle="{StaticResource PaletteTabItem}"
/>
</Grid>
</Grid>
</ControlTemplate>
@@ -24,7 +24,6 @@ namespace mbts.ui.controls.color_picker {
partial class ColorPickerControl : Control {
const string _max_saturation_color_key = "MaxSaturationColor";
readonly PropertyUpdateGate _saturation_border_gate = new PropertyUpdateGate();
readonly ColorPickerVM _color_vm = new ColorPickerVM();
DataGrid _named_colors_grid;
Border _saturation_border;
@@ -34,7 +33,6 @@ namespace mbts.ui.controls.color_picker {
public ColorPickerControl() {
InitializeComponent();
DataContext = _color_vm;
CommandManager.AddExecutedHandler(this, executed_handler);
CommandManager.AddCanExecuteHandler(this, can_execute_handler);
@@ -50,10 +48,10 @@ namespace mbts.ui.controls.color_picker {
_named_colors_grid.CommitEdit();
} else if (e.Parameter is NamedColorVM vm) {
if (e.Command == ColorPickerSwatch.AssignColorCommand)
vm.assign_color(_color_vm.result);
vm.assign_color(Model.result);
else if (e.Command == ColorPickerSwatch.NameColorCommand) {
_named_colors_tab.IsSelected = true;
_color_vm.selected_named_color = vm;
Model.selected_named_color = vm;
_named_colors_grid.ScrollIntoView(vm);
_named_colors_grid.CurrentCell = new DataGridCellInfo(vm, _named_colors_grid.Columns[0]);
}
@@ -64,9 +62,26 @@ namespace mbts.ui.controls.color_picker {
e.CanExecute = true;
}
public event Action<Color> SelectedColorChanged;
public ColorPickerVM Model { get; } = new ColorPickerVM();
public static readonly DependencyProperty ShowControlsProperty =
WPFHelper.create_dp<bool, ColorPickerControl>(nameof(ShowControls), true);
public bool ShowControls {
get => (bool)GetValue(ShowControlsProperty);
set => SetValue(ShowControlsProperty, value);
}
#region SelectedColor
public static readonly DependencyProperty SelectedColorProperty =
WPFHelper.create_dp<Color, ColorPickerControl>(nameof(SelectedColor), (cp, val) => cp._color_vm.result = val);
WPFHelper.create_dp<Color, ColorPickerControl>(
nameof(SelectedColor),
(cp, val) => {
cp.Model.result = val;
cp.SelectedColorChanged?.Invoke(val);
});
public Color SelectedColor { get => (Color)GetValue(SelectedColorProperty); set => SetValue(SelectedColorProperty, value); }
@@ -95,17 +110,17 @@ namespace mbts.ui.controls.color_picker {
// b) destroy and recreate color_vm without breaking TabControl bindings, and ICollectionView source dependencies
void _on_palettes_collection_changed(object sender, NotifyCollectionChangedEventArgs e) {
switch (e.Action) {
case NotifyCollectionChangedAction.Add: _color_vm.palettes.InsertRange(e.NewStartingIndex, e.NewItems); break;
case NotifyCollectionChangedAction.Remove: _color_vm.palettes.RemoveRange(e.OldStartingIndex, e.OldItems.Count); break;
case NotifyCollectionChangedAction.Move: _color_vm.palettes.Move(e.OldStartingIndex, e.NewStartingIndex); break;
case NotifyCollectionChangedAction.Add: Model.palettes.InsertRange(e.NewStartingIndex, e.NewItems); break;
case NotifyCollectionChangedAction.Remove: Model.palettes.RemoveRange(e.OldStartingIndex, e.OldItems.Count); break;
case NotifyCollectionChangedAction.Move: Model.palettes.Move(e.OldStartingIndex, e.NewStartingIndex); break;
case NotifyCollectionChangedAction.Replace:
_color_vm.palettes.RemoveRange(e.OldStartingIndex, e.OldItems.Count);
_color_vm.palettes.InsertRange(e.NewStartingIndex, e.NewItems);
Model.palettes.RemoveRange(e.OldStartingIndex, e.OldItems.Count);
Model.palettes.InsertRange(e.NewStartingIndex, e.NewItems);
break;
case NotifyCollectionChangedAction.Reset:
_color_vm.palettes.Clear();
Model.palettes.Clear();
if (sender is IEnumerable<ColorPaletteVM> items)
_color_vm.palettes.AddRange(items);
Model.palettes.AddRange(items);
break;
}
}
@@ -119,17 +134,17 @@ namespace mbts.ui.controls.color_picker {
_saturation_border = this.assert_template_child<Border>("_saturation_border");
_sample_image = this.assert_template_child<Image>("_sample_image");
// TabItems do not inherit DataContext properly from its visual parent, which breaks certain bindings
_named_colors_grid.SetBinding(DataGrid.ItemsSourceProperty, new Binding(nameof(ColorPickerVM.all_colors_view)) { Source = _color_vm });
_named_colors_grid.SetBinding(DataGrid.SelectedItemProperty, new Binding(nameof(ColorPickerVM.selected_named_color)) { Source = _color_vm });
_color_vm.PropertyChanged += color_vm_property_changed;
_color_vm.result = SelectedColor;
_named_colors_grid.SetBinding(DataGrid.ItemsSourceProperty, new Binding(nameof(ColorPickerVM.all_colors_view)) { Source = Model });
_named_colors_grid.SetBinding(DataGrid.SelectedItemProperty, new Binding(nameof(ColorPickerVM.selected_named_color)) { Source = Model });
Model.PropertyChanged += color_vm_property_changed;
Model.result = SelectedColor;
IDisposable mouse_move_hook = null;
_saturation_border.MouseDown += saturation_border_mouse_down;
_saturation_border.MouseUp += saturation_border_mouse_up;
_template_hooks = Disposable.Create(() => {
_named_colors_grid.clear_bindings(DataGrid.ItemsSourceProperty, DataGrid.SelectedItemProperty);
_color_vm.PropertyChanged -= color_vm_property_changed;
Model.PropertyChanged -= color_vm_property_changed;
_saturation_border.MouseDown -= saturation_border_mouse_down;
_saturation_border.MouseUp -= saturation_border_mouse_up;
Disposable.dispose(ref mouse_move_hook);
@@ -138,7 +153,7 @@ namespace mbts.ui.controls.color_picker {
void color_vm_property_changed(object sender, PropertyChangedEventArgs e) {
switch (e.PropertyName) {
case nameof(ColorPickerVM.max_saturation): _update_saturation_img(); break;
case nameof(ColorPickerVM.result): SelectedColor = _color_vm.result; break;
case nameof(ColorPickerVM.result): SelectedColor = Model.result; break;
}
}
@@ -162,7 +177,7 @@ namespace mbts.ui.controls.color_picker {
var left_adj = ColorUtils.adjust_color(tr, Colors.White, left);
var result = ColorUtils.adjust_color(left_adj, Colors.Black, down);
using (_saturation_border_gate.update())
_color_vm.result = result;
Model.result = result;
}
}
@@ -173,7 +188,7 @@ namespace mbts.ui.controls.color_picker {
void _update_saturation_img() =>
_saturation_border_gate.try_update(() =>
Resources[_max_saturation_color_key] = _color_vm.max_saturation);
Resources[_max_saturation_color_key] = Model.max_saturation);
void _sample_image_click(BitmapSource img, Point pos) {
// https://social.msdn.microsoft.com/Forums/vstudio/en-US/82a5731e-e201-4aaf-8d4b-062b138338fe/getting-pixel-information-from-a-bitmapimage?forum=wpf
@@ -188,9 +203,9 @@ namespace mbts.ui.controls.color_picker {
byte red = pixels[index];
byte green = pixels[index + 1];
byte blue = pixels[index + 2];
byte alpha = pixels[index + 3];
byte alpha = Model.alpha;
var color = Color.FromArgb(alpha, blue, green, red);
_color_vm.result = color;
Model.result = color;
}
void _sample_image_mouse_down(object sender, MouseButtonEventArgs e) {
@@ -0,0 +1,26 @@
<Window
x:Class="mbts.ui.controls.color_picker.ColorPickerDialog"
x:ClassModifier="internal"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:u="clr-namespace:mbts.ui"
xmlns:c="clr-namespace:mbts.ui.controls"
xmlns:cv="clr-namespace:mbts.ui.converters"
xmlns:cp="clr-namespace:mbts.ui.controls.color_picker"
u:WindowStyle.SizeToContent="WidthAndHeight"
u:WindowStyle.AllowResize="False"
Style="{StaticResource BTSDialog}"
>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<cp:ColorPickerControl x:Name="color_picker"/>
<c:DialogSkirtOKCancel
x:Name="dialog_skirt"
Grid.Row="1"
BorderThickness="0"
/>
</Grid>
</Window>
@@ -0,0 +1,47 @@
using bts.utils;
using mbts.spark.utilities;
using mbts.ui.extensions;
using mbts.ui.utility;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Globalization;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
namespace mbts.ui.controls.color_picker {
/// <summary>
/// Interaction logic for ColorPickerControl.xaml
/// </summary>
partial class ColorPickerDialog : Window {
public ColorPickerDialog() =>
InitializeComponent();
public static Color pick_color_modal(Window owner = null, Color? init = null, ObservableCollection<ColorPaletteVM> palettes = null) {
var result = Colors.Transparent;
var cpd = new ColorPickerDialog {
WindowStartupLocation = WindowStartupLocation.CenterOwner
};
if (palettes != null) {
cpd.color_picker.Palettes = palettes;
cpd.color_picker.ShowControls = true;
}
if (init.HasValue)
cpd.color_picker.SelectedColor = init.Value;
cpd.dialog_skirt.OKCommand = new DelegateCommand(() => {
result = cpd.color_picker.SelectedColor;
});
cpd.Owner = owner;
cpd.ShowDialog();
return result;
}
}
}
@@ -0,0 +1,115 @@
<UserControl
x:Class="mbts.ui.controls.color_picker.ColorPickerSuite"
x:ClassModifier="internal"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:u="clr-namespace:mbts.ui"
xmlns:c="clr-namespace:mbts.ui.controls"
xmlns:cv="clr-namespace:mbts.ui.converters"
xmlns:cp="clr-namespace:mbts.ui.controls.color_picker"
xmlns:sys="clr-namespace:System;assembly=mscorlib"
FocusVisualStyle="{x:Null}"
Width="437"
>
<UserControl.Resources>
<!--<sys:Byte x:Key="LowAlpha">0x4C</sys:Byte>-->
<cv:MultiValueConverterPipe x:Key="LowAlphaToolTipVisibilityConverter">
<cv:DynamicIsLessThanConverter/>
<cv:BooleanToObjectConverter ValueForFalse="{x:Static Visibility.Collapsed}" ValueForTrue="{x:Static Visibility.Visible}"/>
</cv:MultiValueConverterPipe>
</UserControl.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<c:TabularColorPicker
x:Name="_tabular_color_picker"
SelectedValuePath="color"
ItemsSource="{x:Static cp:ColorUtils.WebColorVMGrid}"
SelectedValue="{Binding Model.selected_named_color, RelativeSource={RelativeSource AncestorType=cp:ColorPickerSuite}, Mode=TwoWay}"
SelectedOpacity="{Binding Model.opacity, RelativeSource={RelativeSource AncestorType=cp:ColorPickerSuite}}"
>
<c:TabularColorPicker.ItemContainerStyle>
<Style TargetType="ListBoxItem" BasedOn="{StaticResource TabularColorPickerItemStyle}">
<!--ToolTips are not in the same VisualTree as their owner, and for some reason the ListBoxItem's Parent is null,
so there isn't a direct binding path from the ToolTip to the ColorPickerSuite.Model.opacity;
the 'Tag' binding is a workaround to make the ColorPickerSuite available to the ToolTip-->
<Setter Property="Tag" Value="{Binding RelativeSource={RelativeSource AncestorType=cp:ColorPickerSuite}}"/>
<Setter Property="ToolTip">
<Setter.Value>
<ToolTip Content="{Binding}">
<ToolTip.ContentTemplate>
<DataTemplate>
<StackPanel>
<u:BindingProxy x:Name="LowAlphaProxy" Value="{DynamicResource LowAlpha}"/>
<TextBlock Text="{Binding name, Converter={StaticResource TitleCaseConverter}}" HorizontalAlignment="Left"/>
<TextBlock HorizontalAlignment="Left">
<TextBlock.Visibility>
<MultiBinding Converter="{StaticResource LowAlphaToolTipVisibilityConverter}">
<Binding Path="PlacementTarget.Tag.Model.opacity" RelativeSource="{RelativeSource AncestorType=ToolTip}"/>
<Binding Path="Value" ElementName="LowAlphaProxy"/>
</MultiBinding>
</TextBlock.Visibility>
<Run>The color may be difficult to see because the opacity is low.</Run>
<LineBreak/>
<Run FontStyle="Italic">Hint: Use the slider on the right to adjust the opacity level.</Run>
</TextBlock>
</StackPanel>
</DataTemplate>
</ToolTip.ContentTemplate>
</ToolTip>
</Setter.Value>
</Setter>
</Style>
</c:TabularColorPicker.ItemContainerStyle>
</c:TabularColorPicker>
<Grid Grid.Column="1" Margin="4,0,0,0" Width="45" ToolTip="Opacity">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<TextBlock HorizontalAlignment="Right">
<TextBlock.Text>
<MultiBinding StringFormat="P" Converter="{x:Static cv:ValueConverters.SliderPercentage}">
<Binding Path="Value" ElementName="_opacity_slider"/>
<Binding Path="Minimum" ElementName="_opacity_slider"/>
<Binding Path="Maximum" ElementName="_opacity_slider"/>
</MultiBinding>
</TextBlock.Text>
</TextBlock>
<Slider
x:Name="_opacity_slider"
Grid.Row="1"
HorizontalAlignment="Center"
Orientation="Vertical"
Minimum="0"
Maximum="255"
Value="{Binding Model.opacity, RelativeSource={RelativeSource AncestorType=cp:ColorPickerSuite}}"
/>
</Grid>
<c:PopupControl
Grid.Row="1"
Grid.ColumnSpan="2"
Padding="6"
HorizontalAlignment="Stretch"
HorizontalContentAlignment="Left"
BorderThickness="0"
ShowExpanderArrow="False"
PopupPlacement="Bottom"
ButtonStyle="{StaticResource AddItemButton}"
Header="Custom"
>
<cp:ColorPickerControl
x:Name="_color_picker"
Padding="0,0,6,0"
Palettes="{Binding Model.palettes, RelativeSource={RelativeSource AncestorType=cp:ColorPickerSuite}}"
SelectedColor="{Binding Model.selected_color, RelativeSource={RelativeSource AncestorType=cp:ColorPickerSuite}, Mode=TwoWay}"
/>
</c:PopupControl>
</Grid>
</UserControl>
@@ -0,0 +1,144 @@
using bts.utils;
using mbts.ui.shell;
using mbts.ui.utility;
using System;
using System.Collections.ObjectModel;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
namespace mbts.ui.controls.color_picker {
/// <summary>
/// Interaction logic for ColorPickerControl.xaml
/// </summary>
partial class ColorPickerSuite : UserControl {
public ColorPickerSuite() {
Model = new ColorModel(this);
InitializeComponent();
Model.on_palettes(Palettes);
Resources["SelectedOpacity"] = Model.opacity;
Model.PropertyChanged += (s, e) => {
if (e.PropertyName == nameof(Model.opacity))
Resources["SelectedOpacity"] = Model.opacity;
};
//Loaded += on_loaded;
//void on_loaded(object sender, RoutedEventArgs e) =>
// Model.initialize();
}
public static readonly DependencyProperty SelectedColorProperty =
WPFHelper.create_dp<Color, ColorPickerSuite>(
nameof(SelectedColor),
FrameworkPropertyMetadataOptions.BindsTwoWayByDefault,
(o, v) => o.Model.selected_color = v);
public Color SelectedColor {
get => (Color)GetValue(SelectedColorProperty);
set => SetValue(SelectedColorProperty, value);
}
public static readonly DependencyProperty PalettesProperty =
WPFHelper.create_dp<ObservableCollection<ColorPaletteVM>, ColorPickerSuite>(
nameof(Palettes),
(o, v) => {
o.Model.on_palettes(v);
});
public ObservableCollection<ColorPaletteVM> Palettes {
get => GetValue(PalettesProperty) as ObservableCollection<ColorPaletteVM>;
set => SetValue(PalettesProperty, value);
}
public ColorModel Model { get; }
public class ColorModel : ViewModelBase {
readonly ColorPickerSuite _owner;
// Note: 'alpha' and 'opacity' and 'A' (when in the context of 'argb') may be used interchangebly in the comments below.
// -----------------------------------
/// There is an important difference between <see cref="selected_color"/> and <see cref="selected_named_color"/>.
/// The latter is bound to the SelectedValue of the <see cref="TabularColorPicker"/> (TCP), which does not have any opacity input controls.
/// while the former is manually to
/// For this reason, TCP must be immune to alpha channel adjustments, made in other controls (<see cref="ColorPickerSuite"/>, and <see cref="ColorPickerControl"/>, both have alpha/opacity sliders).
/// To achieve this affect, when
/// To achieve this affect, the latter's Alpha should always be 255 (100% opacity).
/// In the <see cref="selected_named_color"/> setter, only the RGB values are forwarded to the 'selected_color' property.
/// This 'alpha buffer' works both ways:
/// - it prevents the TCP from changing the selected_color's alpha to 1 everytime a tabular color item is selected.
/// - and it prevents the alpha sliders from changing TCP's SelectedValue everytime they are adjusted.
///
Color
_selected_color,
_selected_named_color;
IDisposable _palettes_hook;
Action _init;
public ColorModel(ColorPickerSuite owner) {
_owner = owner;
_init = () => {
/// This model demuxes color selection changes from 3 different controls, to a single dependency property (<see cref="SelectedColorProperty"/>).
/// Note the 3 controls are: <see cref="ColorPickerControl"/>, <see cref="TabularColorPicker"/>, <see cref="ColorPickerDialog"/>.
/// Each of the aforementioned controls is bound to one or more properties in this class (<see cref="selected_color"/>, <see cref="selected_named_color"/>, <see cref="palettes"/>).
/// In this design scheme, those controls are the binding targets, and this class the binding source.
/// As the 'binding source,' this class must initialize the 'binding targets' with the appropriate properties.
/// Otherwise, their bogus default values will be assigned to this class, and in turn used to update the binding source of the <see cref="SelectedColorProperty"/>.
/// In other words, the viewmodel's prop that is bound to <see cref="SelectedColorProperty"/> would be overwritten with the bogus default value.
/// To eschew this mess, the code below is executed before the other controls are allowed to affect the <see cref="selected_color"/> property.
/// Thus, they are initialized with the selected color provided by the binding source (aka viewmodel) of the <see cref="SelectedColorProperty"/>, rather than the other way around.
_owner.GetBindingExpression(SelectedColorProperty)?.UpdateTarget();
_owner._color_picker.SelectedColorChanged += c => selected_color = c;
};
}
public Color selected_color {
get => _selected_color;
set {
InvokeOnce.invoke(ref _init);
var alpha = _selected_color.A;
if (Set(ref _selected_color, value)) {
_owner.SetCurrentValue(SelectedColorProperty, value);
if (_selected_color.A != alpha)
RaisePropertyChanged(nameof(opacity));
Set(ref _selected_named_color, value.with(255), nameof(selected_named_color));
}
}
}
public Color selected_named_color {
get => _selected_named_color;
set {
InvokeOnce.invoke(ref _init);
if (Set(ref _selected_named_color, value)) {
if (value != null)
selected_color = value.with(opacity);
}
}
}
public byte opacity {
get => _selected_color.A;
set => selected_color = _selected_color.with(value);
}
public ObservableCollection<ColorPaletteVM> palettes { get; } = new ObservableCollection<ColorPaletteVM>();
bool _is_initialized;
public void initialize() {
if (_is_initialized)
return;
_is_initialized = true;
_owner.GetBindingExpression(SelectedColorProperty)?.UpdateTarget();
_owner._color_picker.SelectedColorChanged += c => selected_color = c;
}
public void on_palettes(ObservableCollection<ColorPaletteVM> new_palettes) {
Disposable.dispose(ref _palettes_hook);
if (new_palettes != null)
_palettes_hook = ObservableAutoWrapper.connect(new_palettes, palettes);
}
}
}
}
@@ -1,28 +1,24 @@
<ListBox
<c:TabularColorPicker
x:Class="mbts.ui.controls.color_picker.ColorPickerSwatch"
x:ClassModifier="internal"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:c="clr-namespace:mbts.ui.controls"
xmlns:cp="clr-namespace:mbts.ui.controls.color_picker"
Margin="0"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
ScrollViewer.HorizontalScrollBarVisibility="Disabled"
>
<ListBox.Resources>
<FrameworkElement.Resources>
<cp:NamedColorItemToolTipConverter x:Key="ToolTipConverter"/>
</ListBox.Resources>
<ListBox.ItemsPanel>
</FrameworkElement.Resources>
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel Orientation="Horizontal"/>
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
<ListBox.ItemContainerStyle>
<Style TargetType="ListBoxItem" BasedOn="{StaticResource {x:Type ListBoxItem}}">
<Setter Property="Cursor" Value="Hand"/>
</Style>
</ListBox.ItemContainerStyle>
<ListBox.ItemTemplate>
</ItemsControl.ItemsPanel>
<!--<ListBox.ItemTemplate>
<DataTemplate>
<Border
BorderBrush="Black"
@@ -40,5 +36,5 @@
</Border.ToolTip>
</Border>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</ListBox.ItemTemplate>-->
</c:TabularColorPicker>
@@ -9,7 +9,7 @@ namespace mbts.ui.controls.color_picker {
/// <summary>
/// Interaction logic for ColorPickerSwatch.xaml
/// </summary>
partial class ColorPickerSwatch : ListBox {
partial class ColorPickerSwatch : TabularColorPicker {
public ColorPickerSwatch() =>
InitializeComponent();
@@ -0,0 +1,123 @@
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:u="clr-namespace:mbts.ui"
xmlns:c="clr-namespace:mbts.ui.controls"
xmlns:cp="clr-namespace:mbts.ui.controls.color_picker"
>
<ControlTemplate x:Key="TabularColorPickerTemplate" TargetType="c:TabularColorPicker">
<Border
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
HorizontalAlignment="{TemplateBinding HorizontalAlignment}"
VerticalAlignment="{TemplateBinding VerticalAlignment}"
Width="{TemplateBinding Width}"
Height="{TemplateBinding Height}"
Padding="{TemplateBinding Padding}"
>
<ItemsPresenter/>
</Border>
</ControlTemplate>
<Style x:Key="TabularColorPickerItemStyle" TargetType="ListBoxItem">
<Setter Property="Padding" Value="0"/>
<Setter Property="BorderThickness" Value="1"/>
<Setter Property="Width" Value="19"/>
</Style>
<DataTemplate x:Key="NamedColorTemplate" DataType="{x:Type cp:NamedColorVM}">
<Border
x:Name="Bd"
BorderThickness="1"
Width="18"
Height="18"
>
<Border.BorderBrush>
<SolidColorBrush Color="{Binding color}" Opacity="{Binding SelectedOpacity, RelativeSource={RelativeSource AncestorType=c:TabularColorPicker}, Converter={x:Static c:TabularColorPicker.AlphaToOpacityConverter}}"/>
</Border.BorderBrush>
<Border.Background>
<SolidColorBrush Color="{Binding color}" Opacity="{Binding SelectedOpacity, RelativeSource={RelativeSource AncestorType=c:TabularColorPicker}, Converter={x:Static c:TabularColorPicker.AlphaToOpacityConverter}}"/>
</Border.Background>
</Border>
<DataTemplate.Triggers>
<DataTrigger Binding="{Binding IsMouseOver, RelativeSource={RelativeSource AncestorType=ListBoxItem}}" Value="True">
<Setter TargetName="Bd" Property="BorderBrush" Value="Black"/>
</DataTrigger>
<DataTrigger Binding="{Binding IsSelected, RelativeSource={RelativeSource AncestorType=ListBoxItem}}" Value="True">
<Setter TargetName="Bd" Property="BorderBrush" Value="Black"/>
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>
<ControlTemplate x:Key="TabularColorPickerItemTemplate" TargetType="ListBoxItem">
<Border
x:Name="RootBorder"
BorderThickness="{TemplateBinding BorderThickness}"
Width="{TemplateBinding Width}"
Height="{TemplateBinding Height}"
HorizontalAlignment="{TemplateBinding HorizontalAlignment}"
VerticalAlignment="{TemplateBinding VerticalAlignment}"
Padding="{TemplateBinding Padding}"
>
<u:StateManager.Groups>
<u:StateGroupsManager AttachToMembers="True">
<u:StateDefinitionCollection>
<u:MouseStateDefinition MouseSource="{Binding Group.Target, RelativeSource={RelativeSource Self}}"/>
</u:StateDefinitionCollection>
</u:StateGroupsManager>
</u:StateManager.Groups>
<VisualStateManager.VisualStateGroups>
<VisualStateGroup Name="MouseStates">
<VisualState Name="Normal"/>
<VisualState Name="MouseOver">
<Storyboard>
<ColorAnimation Storyboard.TargetName="RootBorder" Storyboard.TargetProperty="BorderBrush.Color" BeginTime="0" Duration="0" To="#80FFFFFF"/>
<DoubleAnimation Storyboard.TargetName="RootBorder" Storyboard.TargetProperty="BorderBrush.Opacity" BeginTime="0" Duration="0" To="1"/>
</Storyboard>
</VisualState>
<VisualState Name="Pressed">
<Storyboard>
<DoubleAnimation Storyboard.TargetName="RootBorder" Storyboard.TargetProperty="Background.Opacity" BeginTime="0" Duration="0" To="0.5"/>
<ColorAnimation Storyboard.TargetName="RootBorder" Storyboard.TargetProperty="BorderBrush.Color" BeginTime="0" Duration="0" To="#80FFFFFF"/>
<DoubleAnimation Storyboard.TargetName="RootBorder" Storyboard.TargetProperty="BorderBrush.Opacity" BeginTime="0" Duration="0" To="1"/>
</Storyboard>
</VisualState>
</VisualStateGroup>
<VisualStateGroup Name="SelectionStates">
<VisualState Name="Unselected"/>
<VisualState Name="Selected">
<Storyboard>
<DoubleAnimation Storyboard.TargetName="RootBorder" Storyboard.TargetProperty="BorderBrush.Opacity" BeginTime="0" Duration="0" To="1"/>
</Storyboard>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
<Border.Background>
<SolidColorBrush Color="{Binding color}" Opacity="1"/>
</Border.Background>
<Border.BorderBrush>
<SolidColorBrush Color="Black" Opacity="0"/>
</Border.BorderBrush>
<Grid>
<ContentPresenter
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
/>
</Grid>
</Border>
</ControlTemplate>
<Style TargetType="c:TabularColorPicker">
<Setter Property="Template" Value="{StaticResource TabularColorPickerTemplate}"/>
<Setter Property="ItemsSource" Value="{x:Static cp:ColorUtils.WebColorVMs}"/>
<Setter Property="ItemTemplate" Value="{StaticResource NamedColorTemplate}"/>
<Setter Property="ItemsPanel">
<Setter.Value>
<ItemsPanelTemplate>
<UniformGrid Rows="7"/>
</ItemsPanelTemplate>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>
@@ -0,0 +1,80 @@
using bts.collections;
using bts.proto;
using bts.utils;
using mbts.spark;
using mbts.ui.controls.color_picker;
using mbts.ui.converters;
using mbts.ui.shell;
using mbts.ui.utility;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Globalization;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Data;
using System.Windows.Media;
namespace mbts.ui.controls {
/// <summary>
/// Interaction logic for ColorPickerPopup.xaml
/// </summary>
class TabularColorPicker : ListBox {
public static readonly IValueConverter AlphaToOpacityConverter =
ValueConverter.create<byte, double>(alpha => (double)alpha / 255.0);
static TabularColorPicker() =>
DefaultStyleKeyProperty.OverrideMetadata(typeof(TabularColorPicker), new FrameworkPropertyMetadata(typeof(TabularColorPicker)));
//public static readonly DependencyProperty SelectedColorProperty =
// WPFHelper.create_dp<Color, TabularColorPicker>(
// nameof(SelectedColor),
// FrameworkPropertyMetadataOptions.BindsTwoWayByDefault,
// Colors.Black);
//public Color SelectedColor {
// get => (Color)GetValue(SelectedColorProperty);
// set => SetValue(SelectedColorProperty, value);
//}
//protected override DependencyObject GetContainerForItemOverride() =>
// new TabularColorPickerItem();
public static readonly DependencyProperty SelectedOpacityProperty =
WPFHelper.create_dp<byte, TabularColorPicker>(nameof(SelectedOpacity), 255);
public byte SelectedOpacity {
get => (byte)GetValue(SelectedOpacityProperty);
set => SetValue(SelectedOpacityProperty, value);
}
//protected override DependencyObject GetContainerForItemOverride() =>
// new ListBoxItem();
}
class TabularColorPickerItem : ListBoxItem {
static TabularColorPickerItem() =>
DefaultStyleKeyProperty.OverrideMetadata(typeof(TabularColorPickerItem), new FrameworkPropertyMetadata(typeof(TabularColorPickerItem)));
public static readonly DependencyProperty ColorProperty =
WPFHelper.create_dp<Color, TabularColorPickerItem>(nameof(Color));
public Color Color {
get => (Color)GetValue(ColorProperty);
set => SetValue(ColorProperty, value);
}
public static readonly DependencyProperty ColorNameProperty =
WPFHelper.create_dp<string, TabularColorPickerItem>(nameof(ColorName));
public string ColorName {
get => GetValue(ColorNameProperty) as string;
set => SetValue(ColorNameProperty, value);
}
}
}
@@ -1,4 +1,5 @@
using mbts.ui.converters;
using mbts.ui.dpi;
using mbts.ui.converters;
using mbts.ui.shell;
using mbts.ui.utility.text;
using System;
@@ -10,6 +11,7 @@ using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Media;
using bts;
namespace mbts.ui.controls {
partial class DIYGrid {
@@ -84,12 +86,11 @@ namespace mbts.ui.controls {
}
public static Rect? get_clip(Size content_size, double row_height, double left, double unclipped_left, double width) =>
content_size == null ? get_clip(0, 0, row_height, left, unclipped_left, width) :
get_clip(content_size.Width, content_size.Height, row_height, left, unclipped_left, width);
get_clip(content_size.Width, content_size.Height, row_height, left, unclipped_left, width);
public static Rect? get_clip(double content_width, double content_height, double row_height, double left, double unclipped_left, double width) {
if (content_width > width || unclipped_left < left)
return new Rect(left, 0, width - left + unclipped_left, row_height);
return new Rect(left, 0, width - left + unclipped_left, row_height + 1);
return null;
}
@@ -97,7 +98,10 @@ namespace mbts.ui.controls {
draw_with_clip(dc,
raw_content_width + padding.Left + padding.Right,
raw_content_height + padding.Top + padding.Bottom,
row_height, position.left, position.unclipped_left, position.width);
row_height,
position.left.RoundLayoutValue(),
position.unclipped_left.RoundLayoutValue(),
position.width.RoundLayoutValue());
public static IDisposable draw_with_clip(DrawingContext dc, double content_width, double content_height, double row_height, double left, double unclipped_left, double width) {
var clip = get_clip(content_width, content_height, row_height, left, unclipped_left, width);
@@ -108,54 +112,38 @@ namespace mbts.ui.controls {
}
public static void draw_text(
DrawingContext dc, GlyphContext glyph_context, double font_size, string text, ColumnGeometry position, double row_height,
Brush foreground, Brush background, HorizontalAlignment x_align, VerticalAlignment y_align, Thickness padding) {
DrawingContext dc,
GlyphContext glyph_context,
double font_size,
string text,
ColumnGeometry position,
double row_height,
Brush foreground,
Brush background,
HorizontalAlignment x_align,
VerticalAlignment y_align,
Thickness padding,
bool round_to_layout = true) {
//
if (!string.IsNullOrEmpty(text)) {
var gr = glyph_context.get_glyph_run(text, font_size, position.unclipped_left, 0, position.right, row_height, x_align, y_align, padding);
draw_text(dc, gr, gr.AdvanceWidths.Sum(), font_size, position, padding, row_height, foreground, background);
}
var gr = glyph_context.get_glyph_run(text, font_size, position.unclipped_left, 0, position.right, row_height, x_align, y_align, padding, round_to_layout);
var gwidth = gr == null ? 0 : gr.ComputeAlignmentBox().Width;
draw_text(dc, gr, gwidth, font_size, position, padding, row_height, foreground, background);
}
public static void draw_text(DrawingContext dc, GlyphRun glyph_run, double glyph_width, double font_size, ColumnGeometry position, Thickness padding, double row_height, Brush foreground, Brush background) {
using (draw_with_clip(dc, glyph_width, font_size, padding, row_height, position)) {
if (background != null)
dc.DrawRectangle(background, null, new Rect(position.left, 0, position.width, row_height));
dc.DrawGlyphRun(foreground, glyph_run);
if (glyph_run == null && background == null)
return;
var rect = position.to_rect(row_height);
if (background != null) {
var guidelines = rect.AntiAliasGuidelines(0);
dc.PushGuidelineSet(guidelines);
dc.DrawRectangle(background, null, rect);
dc.Pop();
}
}
public static void draw_text(
DrawingContext dc, GlyphContext glyph_context, double font_size, string text, ColumnGeometry position, double row_height,
Brush foreground, Brush background, HorizontalAlignment x_align, VerticalAlignment y_align, Thickness padding, IEnumerable<CellAnimation> trigger_actions) {
//
using (position.clip_draw(dc, row_height)) {
double adjusted_left = position.unclipped_left;
double available_width = position.width;
if (background != null)
dc.DrawRectangle(background, null, new Rect(position.left, 0, position.width, row_height));
// store any trigger-glyphs in a list, to be drawn after iterating through all triggers (must draw background first, so glyphs will not be covered up).
List<(Brush brush, GlyphRun glyph)> glyphs = new List<(Brush brush, GlyphRun glyph)>(1);
if (trigger_actions != null) {
foreach (CellAnimation ta in trigger_actions) {
switch (ta.animation_type_case) {
case CellAnimation.AnimationTypeOneofCase.BackgroundAnimation:
dc.DrawRectangle(ta.background_animation.brush, ta.background_animation.pen, new Rect(position.left, 0, position.width, row_height));
break;
case CellAnimation.AnimationTypeOneofCase.SymbolAnimation:
var (symbol_text, is_sideways) = _get_symbol_animation_args(ta.symbol_animation);
GlyphRun symbol_glyph = glyph_context.get_glyph_run(symbol_text, font_size, position.width, row_height, HorizontalAlignment.Left, adjusted_left, y_align, 0, is_sideways: is_sideways);
glyphs.Add((ta.symbol_animation.brush, symbol_glyph));
adjusted_left += symbol_glyph.AdvanceWidths.Sum();
break;
}
}
}
foreach (var (brush, glyph) in glyphs) {
dc.DrawGlyphRun(brush, glyph);
}
GlyphRun main_content = glyph_context.get_glyph_run(text, font_size, adjusted_left, 0, position.right, row_height, x_align, y_align, padding);
draw_text(dc, main_content, main_content.AdvanceWidths.Sum(), font_size, position, padding, row_height, foreground, background);
if (glyph_run != null) {
dc.PushClip(new RectangleGeometry(rect));
dc.DrawGlyphRun(foreground, glyph_run);
dc.Pop();
}
}
@@ -13,6 +13,12 @@ using System.Windows.Controls;
using System.Windows.Media;
namespace mbts.ui.controls {
class DIYGridColumn : DIYGrid.ColumnBase<string> {
protected override void on_key_initialized(string key) {
if (id == null && key != null)
id = key.ToString();
}
}
// Column types are nested inside the DIYGrid
partial class DIYGrid {
public static class DefaultColumnWidths {
@@ -1,30 +1,39 @@
using System;
using System.Windows;
using System.Windows.Media;
namespace mbts.ui.controls {
// Column types are nested inside the DIYGrid
partial class DIYGrid {
public struct ColumnGeometry {
public ColumnBase column;
public DIYGridSuperHeader super_column;
public double unclipped_left; /* If the column was unclipped */
public double left;
public double width { get { return column.ActualWidth; } }
public double right { get { return unclipped_left + width; } }
public readonly struct ColumnGeometry {
public readonly ColumnBase column;
public readonly DIYGridSuperHeader super_column;
public readonly double
left,
// If the column was unclipped
unclipped_left,
right;
public ColumnGeometry(ColumnBase column, DIYGridSuperHeader super_column, double left, double right)
: this(column, super_column, left, left, right) { }
public ColumnGeometry(ColumnBase column, DIYGridSuperHeader super_column, double left, double unclipped_left, double right) {
this.column = column;
this.super_column = super_column;
this.left = left;
this.unclipped_left = left;
this.right = right;
}
public double width => right - left;
public bool is_partially_obscured { get { return unclipped_left < left; } }
public double visible_width => Math.Max(0, width - left + unclipped_left);
public override string ToString() {
return "{3} {0:0.0},{1:0.0} w{2:0.0}".fmt(left, right, width, column.Header);
}
public string group { get { return super_column == null ? "" : super_column.FullHeader; } }
public RectangleGeometry get_clip(double row_height) =>
new RectangleGeometry(new Rect(left, 0, width - left + unclipped_left, row_height));
public Rect to_rect(double row_height) =>
new Rect(left, 0, width, row_height);
public IDisposable clip_draw(DrawingContext dc, double row_height) {
dc.PushClip(get_clip(row_height));
return Disposable.Create(dc.Pop);
public override string ToString() {
return "{3} {0:0.0},{1:0.0} w{2:0.0}".fmt(left, right, width, column.Header);
}
}
}
@@ -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;
}
@@ -98,9 +98,10 @@ namespace mbts.ui.settings {
public GridColumnSettings with(
string display_name = null, bool? is_visible = null, bool? is_collapsed = null, NullableFontWeightProto? font_weight_override = null, NullableColorProto? font_color_override = null,
double? width = null, HorizontalAlignment? horizontal_alignment = null, double? horizontal_offset = null, NullableColorProto? background_color_override = null) =>
double? width = null, HorizontalAlignment? horizontal_alignment = null, double? horizontal_offset = null, NullableColorProto? background_color_override = null,
string id = null) =>
//
new GridColumnSettings(_id,
new GridColumnSettings(id ?? _id,
display_name ?? this.display_name,
is_visible ?? this.is_visible,
is_collapsed ?? this.is_collapsed,
@@ -1,18 +1,13 @@
using bts.collections;
using bts.proto;
using mbts.collections;
using bts.proto;
using bts.utils;
using mbts.ui;
using mbts.ui.controls;
using mbts.ui.settings;
using mbts.ui.shell;
using mbts.ui.utility;
using mbts.ui.viewmodels;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reactive.Linq;
using System.Collections.ObjectModel;
using System.Windows;
using System.Windows.Media;
using CellAnimation = mbts.ui.CellAnimation;
namespace mbts.spark {
/// <summary>
@@ -28,172 +23,122 @@ namespace mbts.spark {
ImmutableFontColor = 1,
ImmutableFontWeight = 1 << 1,
ImmutableHeader = 1 << 2,
ImmutableBackgroundColor = 1 << 3
ImmutableBackgroundColor = 1 << 3,
ImmutableVisibility = 1 << 4
}
partial class GridSettingsVM<TRowData, TPredicateData, TSettingsData> {
public interface IColumnVM : ISimpleColumnVM, IValueFactory<(GridColumnSettings settings, ILookup<TPredicateData, CellAnimation> animations)>, IDisposable {
ObservableEditableCollection<PredicateCellAnimationVM> predicate_animations { get; }
new (GridColumnSettings settings, ILookup<TPredicateData, CellAnimation> animations) create();
new void load_from((GridColumnSettings settings, ILookup<TPredicateData, CellAnimation> animations) args);
class GridColumnSettingsVM : DisposableViewModelBase {
string _id = string.Empty;
GridColumnSettings _settings;
public GridColumnSettingsVM()
: this(GridColumnSettingsFeatureFlags.None) { }
protected GridColumnSettingsVM(GridColumnSettingsFeatureFlags? init_flags) {
if (font_color.override_value.A == 0)
font_color.set_override(font_color.override_value.with(255));
if (init_flags.HasValue)
apply_flags(init_flags.Value);
}
public class ColumnVM : DisposableViewModelBase, IColumnVM {
public readonly GridSettingsVM<TRowData, TPredicateData, TSettingsData> owner;
public string id { get => _id; private set => Set(ref _id, value); }
public StringOverrideParameterVM header { get; } = new StringOverrideParameterVM("Header");
public BooleanOverrideParameterVM show { get; } = new BooleanOverrideParameterVM("Show");
public DoubleParameterVM width { get; } = new DoubleParameterVM("Width");
public ColorOverrideParameterVM background { get; } = new ColorOverrideParameterVM("Background");
public ColorOverrideParameterVM font_color { get; } = new ColorOverrideParameterVM("Font Color");
public FontWeightOverrideParameterVM font_weight { get; } = new FontWeightOverrideParameterVM();
public HorizontalAlignmentParameterVM horizontal_alignment { get; } = new HorizontalAlignmentParameterVM("Horizontal Alignment");
// display name
public ObservableCollection<object> parameters { get; } = new ObservableCollection<object>();
/// Note: parameter visibility is not automatically re-evaluated (<see cref="sync_params"/>).
public bool show_width { get; set; } = true;
public bool show_horizontal_alignment { get; set; } = true;
public bool can_edit_header { get; set; } = true;
public bool can_edit_width { get; set; } = true;
public bool can_edit_horizontal_alignment { get; set; } = true;
string
_id = string.Empty,
_display_name = string.Empty;
double _width = 70;
HorizontalAlignment _horizontal_alignment = HorizontalAlignment.Left;
bool
_is_visible,
_can_override_display_name = true,
_can_override_font_color = true,
_is_font_color_overridden,
_can_override_font_weight = true,
_is_font_weight_overridden,
_can_override_background_color = true,
_is_background_color_overridden;
FontWeight _font_weight_override;
GridColumnSettings _settings;
public ColumnVM(GridSettingsVM<TRowData, TPredicateData, TSettingsData> owner) {
this.owner = owner;
// animations
predicate_animations = new ObservableEditableCollection<PredicateCellAnimationVM>(() =>
new PredicateCellAnimationVM(id, owner._create_column_predicate_vm(id), owner._predicate_data_factory), true);
predicate_animations.CollectionChanged += WPFHelper.create_collection_changed_cb(on_items_added: (_, __) => owner.show_cell_predicate_animations = true);
_dispose = Disposable.Create(() => predicate_animations.OfType<IDisposable>().DisposeAll());
}
public string id { get => _id; private set => Set(ref _id, value); }
public double width { get => _width; set => Set(ref _width, value); }
public HorizontalAlignment horizontal_alignment { get => _horizontal_alignment; set => Set(ref _horizontal_alignment, value); }
public bool is_visible { get => _is_visible; set => Set(ref _is_visible, value); }
// display name
public bool can_override_display_name { get => _can_override_display_name; private set => Set(ref _can_override_display_name, value); }
public string display_name { get => _display_name; set => Set(ref _display_name, value); }
// font color override
public bool can_override_font_color { get => _can_override_font_color; private set => Set(ref _can_override_font_color, value); }
public bool is_font_color_overridden { get => _is_font_color_overridden; set => Set(ref _is_font_color_overridden, value); }
public ColorPickerPopupVM font_color_override { get; } = new ColorPickerPopupVM();
// font weight override
public bool can_override_font_weight { get => _can_override_font_weight; private set => Set(ref _can_override_font_weight, value); }
public bool is_font_weight_overridden { get => _is_font_weight_overridden; set => Set(ref _is_font_weight_overridden, value); }
public FontWeight font_weight_override { get => _font_weight_override; set => Set(ref _font_weight_override, value); }
// background color override
public bool can_override_background_color { get => _can_override_background_color; private set => Set(ref _can_override_background_color, value); }
public bool is_background_color_overridden { get => _is_background_color_overridden; set => Set(ref _is_background_color_overridden, value); }
public ColorPickerPopupVM background_color_override { get; } = new ColorPickerPopupVM() { bind_opacity_to_alpha = true };
// animations
public ObservableEditableCollection<PredicateCellAnimationVM> predicate_animations { get; }
public (GridColumnSettings settings, ILookup<TPredicateData, CellAnimation> animations) create() {
var settings = _settings.with(
can_override_display_name ? display_name : null,
is_visible,
null,
is_font_weight_overridden ? new NullableFontWeightProto(font_weight_override) : new NullableFontWeightProto(null),
is_font_color_overridden ? new NullableColorProto(font_color_override.color) : new NullableColorProto(null),
width,
horizontal_alignment,
background_color_override: is_background_color_overridden ?
new NullableColorProto(background_color_override.color) : default);
var animations_lookup = predicate_animations.ToFlatLookup(pa => pa.predicate.create_assigned(owner._predicate_data_factory), pa => pa.animations.Select(a => a.create()));
return (settings, animations_lookup);
}
public void load_from((GridColumnSettings settings, ILookup<TPredicateData, CellAnimation> animations) value) {
_settings = value.settings;
id = _settings.id;
display_name = _settings.display_name;
width = _settings.width;
horizontal_alignment = _settings.horizontal_alignment;
is_visible = _settings.is_visible;
// font color
is_font_color_overridden = _settings.font_color_override.HasValue;
if (is_font_color_overridden) {
font_color_override.load_from(new SolidColorBrush(_settings.font_color_override.Value));
}
// font weight
is_font_weight_overridden = _settings.font_weight_override.HasValue;
if (is_font_weight_overridden) {
_font_weight_override = _settings.font_weight_override.Value;
}
// background color
is_background_color_overridden = _settings.background_color_override.HasValue;
if (is_background_color_overridden) {
background_color_override.color = _settings.background_color_override.Value;
}
predicate_animations.SetItems(value.animations.empty_if_null().Select(g => {
var vm = new PredicateCellAnimationVM(id, owner._create_column_predicate_vm(id), owner._predicate_data_factory);
vm.load_from((g.Key, g));
return vm;
}));
}
public void apply_flags(GridColumnSettingsFeatureFlags flags) {
// display name
can_override_display_name = (flags & GridColumnSettingsFeatureFlags.ImmutableHeader) == 0;
if (!can_override_display_name)
display_name = id;
// font color
can_override_font_color = (flags & GridColumnSettingsFeatureFlags.ImmutableFontColor) == 0;
is_font_color_overridden &= can_override_font_color;
// font weight
can_override_font_weight = (flags & GridColumnSettingsFeatureFlags.ImmutableFontWeight) == 0;
is_font_weight_overridden &= can_override_font_weight;
// background color
can_override_background_color = (flags & GridColumnSettingsFeatureFlags.ImmutableBackgroundColor) == 0;
is_background_color_overridden &= can_override_background_color;
}
public override string ToString() => $"{display_name} ({id})";
GridColumnSettings IValueFactory<GridColumnSettings>.create() =>
create().settings;
void IValueFactory<GridColumnSettings>.load_from(GridColumnSettings value) =>
load_from((value, new Dictionary<TPredicateData, CellAnimation>().ToLookup(kv => kv.Key, kv => kv.Value)));
}
public class PredicateCellAnimationVM : DisposableViewModelBase, IValueFactory<(TPredicateData predicate, IEnumerable<CellAnimation> animations)> {
//
readonly string _col_id;
readonly Property<bool> _show_animations;
readonly Func<TPredicateData> _predicate_data_factory;
public PredicateCellAnimationVM(string col_id, IPropertyWrapperVM<TPredicateData> predicate, Func<TPredicateData> predicate_data_factory) {
_col_id = col_id;
this.predicate = predicate;
_predicate_data_factory = predicate_data_factory;
_show_animations = backing(nameof(show_animations), true);
animations = new ObservableEditableCollection<CellAnimationVM>(() => {
var vm = new CellAnimationVM(col_id);
vm.selected_animation_type = vm.animation_types.FirstOrDefault();
return vm;
}, true);
animations.CollectionChanged += WPFHelper.create_collection_changed_cb(on_items_added: (_, __) => _show_animations.set(true));
_dispose = predicate as IDisposable;
}
public IPropertyWrapperVM<TPredicateData> predicate { get; }
public ObservableEditableCollection<CellAnimationVM> animations { get; }
public IEnumerable<CellAnimation.AnimationTypeOneofCase> animation_types { get; } = EnumHelper.get_values_except(CellAnimation.AnimationTypeOneofCase.None);
public bool show_animations { get => _show_animations.Value; set => _show_animations.set(value); }
public (TPredicateData predicate, IEnumerable<CellAnimation> animations) create() =>
(predicate.create_assigned(_predicate_data_factory), animations.Select(a => a.create()));
public void load_from((TPredicateData predicate, IEnumerable<CellAnimation> animations) args) {
predicate.load_from(args.predicate);
animations.SetItems(args.animations.Select(a => {
var vm = new CellAnimationVM(_col_id);
vm.load_from(a);
return vm;
}));
protected virtual IEnumerable<object> _visible_parameters {
get {
if (header.can_override)
yield return header;
if (show_width)
yield return width;
if (show_horizontal_alignment)
yield return horizontal_alignment;
if (background.can_override)
yield return background;
if (font_color.can_override)
yield return font_color;
if (font_weight.can_override)
yield return font_weight;
}
}
public GridColumnSettings create() {
return _settings.with(
header.effective_is_overridden ? header.override_value : id,
show.effective_value,
null,
_get_optional_font_weight_proto(font_weight),
_get_optional_color_proto(font_color),
width.value,
horizontal_alignment.value,
null,
_get_optional_color_proto(background));
}
public void load_from(GridColumnSettings value) {
_settings = value;
id = _settings.id;
header.default_value = _settings.id;
header.override_value = _settings.display_name;
width.value = _settings.width;
horizontal_alignment.value = _settings.horizontal_alignment;
show.override_value = value.is_visible;
// font color
font_color.is_overridden = _settings.font_color_override.HasValue;
if (font_color.can_override) {
if (font_color.is_overridden)
font_color.override_value = _settings.font_color_override.Value;
}
// font weight
font_weight.is_overridden = _settings.font_weight_override.HasValue;
if (font_weight.is_overridden)
font_weight.override_value = _settings.font_weight_override.Value;
// background
background.is_overridden = _settings.background_color_override.HasValue;
if (background.is_overridden)
background.override_value = _settings.background_color_override.Value;
}
public void apply_flags(GridColumnSettingsFeatureFlags flags) {
header.can_override = (flags & GridColumnSettingsFeatureFlags.ImmutableHeader) == 0;
show.can_override = (flags & GridColumnSettingsFeatureFlags.ImmutableVisibility) == 0;
font_weight.can_override = (flags & GridColumnSettingsFeatureFlags.ImmutableFontWeight) == 0;
font_color.can_override = (flags & GridColumnSettingsFeatureFlags.ImmutableFontColor) == 0;
background.can_override = (flags & GridColumnSettingsFeatureFlags.ImmutableBackgroundColor) == 0;
parameters.sync_with(_visible_parameters);
}
public void sync_params() =>
parameters.sync_with(_visible_parameters);
public override string ToString() =>
header.ToString();
static NullableColorProto? _get_optional_color_proto(ColorOverrideParameterVM optional_color) =>
optional_color.can_override ?
optional_color.is_overridden ?
new NullableColorProto(optional_color.override_value) :
new NullableColorProto() :
(NullableColorProto?)null;
static NullableFontWeightProto? _get_optional_font_weight_proto(ParameterOverrideVM<FontWeight> optional_font_weight) =>
optional_font_weight.can_override ?
optional_font_weight.is_overridden ?
new NullableFontWeightProto(optional_font_weight.override_value) :
new NullableFontWeightProto() :
(NullableFontWeightProto?)null;
}
}
@@ -16,7 +16,7 @@ namespace mbts.ui.settings {
const string _default_font_family = "Segoe UI";
const double _default_font_size = 11;
const uint
_default_font_color = 0,
_default_font_color = 0xFF000000,
_default_background = 0x00FFFFFF;
const ushort _default_font_weight = 400;
const uint _default_background_color = 0x00FFFFFF; // Colors.Transparent
@@ -1,12 +1,8 @@
using bts;
using bts.api;
using bts.collections;
using bts.shell;
using bts.utils;
using mbts.spark;
using mbts.ui.settings;
using mbts.ui.shell;
using mbts.utils;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
@@ -16,73 +12,31 @@ using System.Windows.Media;
using static mbts.ui.controls.DIYGrid;
namespace mbts.ui {
class GridSettingsManager<TRow, TRowData, TPredicateData, TSettingsData> :
ViewModelBase, IValueFactory<TSettingsData>, IObserver<TSettingsData>, IDisposable
where TRow : IRowVM
where TSettingsData : class, IGridSettingsWithNotifications<TRowData, TPredicateData> {
class GridSettingsManager : ViewModelBase {
//
readonly IBus _bus;
readonly OrdersModel _orders_model;
readonly List<IDisposable> _notification_subs = new List<IDisposable>();
readonly ObservableCollection<ColumnBase> _columns;
readonly IEnumerable<TRow> _rows;
readonly Property<GridSettings> _grid_settings;
readonly DelegateProperty<FontFamily> _font_family;
readonly DelegateProperty<FontWeight> _font_weight;
readonly DelegateProperty<double> _font_size;
readonly DelegateProperty<SolidColorBrush> _font_color, _selection_highlight, _background_color;
readonly SetMap<IPredicate<TRowData>, AnimationDefinitionWrapper<TRow>> _animation_defs = new SetMap<IPredicate<TRowData>, AnimationDefinitionWrapper<TRow>>();
readonly Func<TPredicateData> _predicate_data_factory;
readonly Func<TPredicateData, IPredicate<TRowData>> _predicate_factory;
readonly Func<TPredicateData, INotificationPredicate<TRowData>> _notification_predicate_factory;
readonly Func<IPropertyWrapperVM<TPredicateData>> _create_predicate_vm;
readonly Func<string, IPropertyWrapperVM<TPredicateData>> _create_column_predicate_vm;
readonly Func<GridSettings, IEnumerable<IPredicateAnimationsData<TRowData, TPredicateData>>, TSettingsData> _create_settings_obj;
readonly Func<IPredicate<TRowData>, SoundNotification, IDisposable> _link_sound;
readonly Func<INotificationPredicate<TRowData>, IDisposable> _link_toast_notification;
readonly IReadOnlyDictionary<string, ImmutableColumnSettingStore> _column_flags;
IEnumerable<IPredicateAnimationsData<TRowData, TPredicateData>> _predicate_animations_data;
public GridSettingsManager(
IBus bus,
OrdersModel orders_model,
IEnumerable<TRow> rows,
ObservableCollection<ColumnBase> columns,
Func<TPredicateData> predicate_data_factory,
Func<IPropertyWrapperVM<TPredicateData>> create_predicate_vm,
Func<string, IPropertyWrapperVM<TPredicateData>> create_column_predicate_vm,
Func<GridSettings, IEnumerable<IPredicateAnimationsData<TRowData, TPredicateData>>, TSettingsData> create_settings_obj,
Func<TPredicateData, IPredicate<TRowData>> predicate_factory,
Func<TPredicateData, INotificationPredicate<TRowData>> notification_predicate_factory,
Func<IPredicate<TRowData>, SoundNotification, IDisposable> link_sound,
Func<INotificationPredicate<TRowData>, IDisposable> link_toast_notification,
IReadOnlyDictionary<string, GridColumnSettingsFeatureFlags> column_flags = null) {
//
_bus = bus;
_orders_model = orders_model;
_rows = rows;
_columns = columns;
_predicate_data_factory = predicate_data_factory;
_predicate_factory = predicate_factory;
_notification_predicate_factory = notification_predicate_factory;
_create_predicate_vm = create_predicate_vm;
_create_column_predicate_vm = create_column_predicate_vm;
_create_settings_obj = create_settings_obj;
_link_sound = link_sound;
_link_toast_notification = link_toast_notification;
_column_flags = column_flags.ToDictionary(kv => kv.Key, kv => ImmutableColumnSettingStore.create(kv.Value));
_column_flags = column_flags?.ToDictionary(kv => kv.Key, kv => ImmutableColumnSettingStore.create(kv.Value));
_grid_settings = backing(nameof(grid_settings), GridSettings.Default);
_font_family = backing(nameof(font_family), () => new FontFamily(grid_settings.font_family), nameof(grid_settings));
_font_weight = backing(nameof(font_weight), () => grid_settings.font_weight, nameof(grid_settings));
_font_size = backing(nameof(font_size), () => grid_settings.font_size, nameof(font_size));
_font_color = backing(nameof(font_color), () => grid_settings.font_color.rgb_solid_brush().frozen(), nameof(grid_settings));
_selection_highlight = backing(nameof(selection_highlight), () => grid_settings.selection_highlight.to_brush().frozen(), nameof(grid_settings));
_selection_highlight = backing(nameof(selection_highlight), () => grid_settings.selection_highlight.to_argb_brush().frozen(), nameof(grid_settings));
_background_color = backing(nameof(background_color), () => grid_settings.background_color.argb_color().to_solid_brush().frozen(), nameof(grid_settings));
}
public void Dispose() =>
Disposable.dispose(_notification_subs);
public GridSettings grid_settings => _grid_settings.Value;
public FontFamily font_family => _font_family.Value;
public FontWeight font_weight => _font_weight.Value;
@@ -91,52 +45,25 @@ namespace mbts.ui {
public SolidColorBrush selection_highlight => _selection_highlight.Value;
public SolidColorBrush background_color => _background_color.Value;
public GridSettingsVM<TRowData, TPredicateData, TSettingsData> create_vm(GridSettings settings) {
var vm = new GridSettingsVM<TRowData, TPredicateData, TSettingsData>(_bus, _orders_model, _predicate_data_factory, _notification_predicate_factory, _create_predicate_vm, _create_column_predicate_vm, _create_settings_obj, _column_flags.ToDictionary(kv => kv.Key, kv => kv.Value.flags));
vm.load(_create_settings_obj(settings, _predicate_animations_data));
return vm;
public GridSettings create() {
try {
_grid_settings.set(grid_settings.with(columns: _columns.Select(c => c.create())));
} catch (Exception ex) {
Log.Ui.warn($"Error refreshing grid settings (may be benign, if caused by accessing a ColumnBase property after window closed): {ex}.");
}
return grid_settings;
}
public TSettingsData create() {
_grid_settings.set(grid_settings.with(_columns.Select(c => {
var col_settings = c.create();
if (_column_flags.TryGetValue(c.id, out ImmutableColumnSettingStore store))
col_settings = store.apply(col_settings);
return col_settings;
})));
return _create_settings_obj(grid_settings, _predicate_animations_data);
}
public GridSettingsVM create_vm() =>
new GridSettingsVM(_column_flags?.ToDictionary(kv => kv.Key, kv => kv.Value.flags));
public void load_from(TSettingsData value) => _load_from_core(value, true);
void _load_from_core(TSettingsData value, bool reset_immutables) {
Disposable.dispose(_notification_subs);
var grid = value.general ?? GridSettings.Default;
public void load_from(GridSettings grid) {
_grid_settings.set(grid);
foreach (ColumnBase col in _columns)
col.set_fallback_font_params(new FontFamily(grid.font_family), grid.font_size, grid.font_color.rgb_color(), grid.font_weight, grid.background_color.argb_color());
_predicate_animations_data = value.predicate_animations;
_animation_defs.clear();
foreach (var pa in _predicate_animations_data) {
var predicate = _predicate_factory(pa.predicate_data);
if (predicate == null)
continue;
foreach (var s in pa.sound_notifications) {
_notification_subs.Add(_link_sound(predicate, s));
}
var notification_predicate = _notification_predicate_factory(pa.predicate_data);
if (notification_predicate != null)
_notification_subs.Add(_link_toast_notification(notification_predicate));
}
// ** in the unfortunate event that we botch backwards compatability, and the columns don't bear a 1:1 relationship with the column-settings
// don't add insult to injury by crashing **
if (grid_settings.columns != null && _columns != null) {
if (reset_immutables) {
foreach (var kv in _column_flags) {
if (grid.columns.TryGetFirst(c => c.id == kv.Key, out GridColumnSettings cs))
kv.Value.store(cs);
}
}
var col_settings_id_map = grid_settings.columns.ToDictionary(cs => id_key(cs.id));
var columns_id_map = _columns.ToDictionary(c => id_key(c.id));
if (col_settings_id_map.Count == columns_id_map.Count && col_settings_id_map.Count == grid_settings.columns.Count) {
@@ -150,31 +77,8 @@ namespace mbts.ui {
col.load_from(cs);
}
// invalidate all the rows to draw them again to reflect the latest settings
foreach (var r in _rows) r.raise_invalidated();
//
string id_key(string id) => id.ToLower();
}
}
void IObserver<TSettingsData>.OnNext(TSettingsData sd) {
if (sd == null) return;
try {
var grid = sd.general;
if (grid.columns != null) {
var cols_with_applied_flags = grid.columns.ToList();
for (int i = 0; i < cols_with_applied_flags.Count; i++) {
if (_column_flags.TryGetValue(cols_with_applied_flags[i].id, out ImmutableColumnSettingStore store)) {
cols_with_applied_flags[i] = store.apply(cols_with_applied_flags[i]);
}
}
sd = _create_settings_obj(grid.with(cols_with_applied_flags), sd.predicate_animations);
}
_load_from_core(sd, false);
} catch (Exception ex) {
Log.Ui.error($"Error loading GridSettings (as observer): {ex}");
}
}
void IObserver<TSettingsData>.OnError(Exception error) { }
void IObserver<TSettingsData>.OnCompleted() { }
}
}
File diff suppressed because it is too large. Load diff
@@ -0,0 +1,17 @@
using mbts.ui.converters;
using System.Windows;
using System.Windows.Data;
namespace mbts.ui.spark {
/// <summary>
/// Interaction logic for GridSettingsView.xaml
/// </summary>
partial class GridSettingsResources : ResourceDictionary {
public GridSettingsResources() =>
InitializeComponent();
public static IMultiValueConverter ColumnDisplayNameVisibilityConverter =>
ValueConverter.create<bool, string, string, Visibility>((show_display_name, id, header) =>
!show_display_name || id == header ? Visibility.Collapsed : Visibility.Visible);
}
}
@@ -1,23 +1,22 @@
using bts.api;
using bts.collections;
using bts.proto;
using bts.shell;
using bts.utils;
using mbts.collections;
using mbts.ui;
using mbts.ui.controls;
using mbts.ui.settings;
using mbts.ui.shell;
using mbts.ui.utility;
using mbts.ui.utility.text;
using mbts.ui.viewmodels;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Linq;
using System.Reactive.Linq;
using System.Windows;
using CellAnimation = mbts.ui.CellAnimation;
using FontFamily = System.Windows.Media.FontFamily;
using System.Windows.Media;
namespace mbts.spark {
interface IGridSettingsWithNotifications<TRowData, TPredicateData> {
@@ -45,70 +44,52 @@ namespace mbts.spark {
EqualityComparer.safe_aggregate_hash(predicate_animations_data));
}
partial class GridSettingsVM<TRowData, TPredicateData, TSettingsData> : DisposableViewModelBase, IRequestViewClose, IWindowSettingsVM<TSettingsData>, ITemplateSelectorKey
where TSettingsData :class, IGridSettingsWithNotifications<TRowData, TPredicateData> {
class GridSettingsVM : DisposableViewModelBase, IRequestViewClose, IWindowSettingsVM<GridSettings>, ITemplateSelectorKey {
//
protected readonly Func<IPropertyWrapperVM<TPredicateData>> _create_predicate_vm;
protected readonly Func<string, IPropertyWrapperVM<TPredicateData>> _create_column_predicate_vm;
readonly Func<TPredicateData> _predicate_data_factory;
readonly Func<GridSettings, IEnumerable<IPredicateAnimationsData<TRowData, TPredicateData>>, TSettingsData> _create_settings_obj;
readonly Property<IColumnVM> _current_column;
readonly Property<FontFamily> _font_family;
readonly Property<FontWeight> _font_weight;
readonly Property<string> _font_display_name;
readonly Property<double> _font_size;
readonly Property<bool> _show_predicate_animations, _show_cell_predicate_animations, _show_background_color;
readonly OrdersModel _orders_model;
readonly FontParameterVM _font_vm = new FontParameterVM("Cell Font");
readonly ColorParameterVM
_font_color = new ColorParameterVM("Cell Font Color"),
_selection_highlight = new ColorParameterVM("Selection Highlight"),
_background = new ColorParameterVM("Background");
readonly Property<GridColumnSettingsVM> _current_column;
readonly IReadOnlyDictionary<string, GridColumnSettingsFeatureFlags> _column_flags;
readonly ObservableCollection<object> _parameters = new ObservableCollection<object>();
bool _background_enabled, _show_column_display_name;
public GridSettingsVM(
IBus bus,
OrdersModel orders_model,
Func<TPredicateData> predicate_data_factory,
Func<TPredicateData, INotificationPredicate<TRowData>> notification_predicate_factory,
Func<IPropertyWrapperVM<TPredicateData>> create_predicate_vm,
Func<string, IPropertyWrapperVM<TPredicateData>> create_column_predicate_vm,
Func<GridSettings, IEnumerable<IPredicateAnimationsData<TRowData, TPredicateData>>, TSettingsData> create_settings_obj,
IReadOnlyDictionary<string, GridColumnSettingsFeatureFlags> column_flags = null) {
public GridSettingsVM(IReadOnlyDictionary<string, GridColumnSettingsFeatureFlags> column_flags = null) {
//
this.bus = bus;
_orders_model = orders_model;
_predicate_data_factory = predicate_data_factory;
_create_predicate_vm = create_predicate_vm;
_create_column_predicate_vm = create_column_predicate_vm;
_create_settings_obj = create_settings_obj;
_column_flags = column_flags ?? new Dictionary<string, GridColumnSettingsFeatureFlags>();
_font_family = backing(nameof(font_family), TextUtils.StandardFontFamily);
_font_size = backing(nameof(font_size), FontSizes.Small);
_font_weight = backing(nameof(font_weight), FontWeights.Normal);
_font_display_name = backing(nameof(font_display_name), () => TextUtils.display_name(font_family, font_size), new[] { nameof(font_family), nameof(font_size) });
_show_predicate_animations = backing(nameof(show_predicate_animations), true);
_show_cell_predicate_animations = backing(nameof(show_cell_predicate_animations), true);
_show_background_color = backing(nameof(show_background_color), false);
_current_column = backing<IColumnVM>(nameof(current_column));
predicate_animations = new ObservableEditableCollection<PredicateRowAnimationVM>(() =>
new PredicateRowAnimationVM(_create_predicate_vm(), _predicate_data_factory), true);
predicate_animations.CollectionChanged += WPFHelper.create_collection_changed_cb(on_items_added: (_, __) => _show_predicate_animations.set(true));
_current_column = backing<GridColumnSettingsVM>(nameof(current_column));
_parameters.sync_with(_visible_parameters);
// font
_dispose = ObservableAutoWrapper.connect_item_hooks(columns, c => {
_font_vm.PropertyChanged += on_prop_changed;
_background.PropertyChanged += on_prop_changed;
_font_color.Changed += on_font_color_changed;
on_font_color_changed(default, _font_color.value);
on_prop_changed(null, null);
return Disposable.Create(() => {
_font_vm.PropertyChanged -= on_prop_changed;
_font_color.Changed -= on_font_color_changed;
});
// set the whole state everytime, bc it's simpler and less error prone
// than coordinating between specific properties.
void on_prop_changed(object sender, PropertyChangedEventArgs e) {
c.background.default_value = _background.value;
c.font_weight.family = _font_vm.family;
c.font_weight.default_value = _font_vm.weight;
c.font_weight.size = _font_vm.size;
}
void on_font_color_changed(Color oldval, Color newval) =>
c.font_color.default_value = newval;
});
}
public event EventHandler RequestClose;
public event Action committed;
public IBus bus { get; }
public ObservableCollection<IColumnVM> columns { get; } = new ObservableCollection<IColumnVM>();
public IColumnVM current_column { get => _current_column.Value; set => _current_column.set(value); }
public string font_display_name => _font_display_name.Value;
public FontFamily font_family { get => _font_family.Value; set => _font_family.set(value); }
public FontWeight font_weight { get => _font_weight.Value; set => _font_weight.set(value); }
public ColorPickerPopupVM font_color { get; } = new ColorPickerPopupVM();
public ColorPickerPopupVM selection_highlight { get; } = new ColorPickerPopupVM();
public ColorPickerPopupVM background_color { get; } = new ColorPickerPopupVM();
public bool show_background_color => _show_background_color.Value;
public double font_size { get => _font_size.Value; set => _font_size.set(value); }
public bool enable_sound_triggers => true;
public bool show_predicate_animations { get => _show_predicate_animations.Value; set => _show_predicate_animations.set(value); }
public bool show_cell_predicate_animations { get => _show_cell_predicate_animations.Value; set => _show_cell_predicate_animations.set(value); }
public ObservableEditableCollection<PredicateRowAnimationVM> predicate_animations { get; }
public ObservableCollection<GridColumnSettingsVM> columns { get; } = new ObservableCollection<GridColumnSettingsVM>();
public GridColumnSettingsVM current_column { get => _current_column.Value; set => _current_column.set(value); }
public IReadOnlyList<DialogSkirtItem> dialog_skirt_items => new[] {
new DialogSkirtItem("OK", new DelegateCommand(() => {
committed?.Invoke();
@@ -117,119 +98,81 @@ namespace mbts.spark {
new DialogSkirtItem("Cancel", new DelegateCommand(() => RequestClose?.Invoke(this, new EventArgs()))) { is_cancel = true },
new DialogSkirtItem("Apply", new DelegateCommand(() => committed?.Invoke()))
};
public bool show_column_display_name {
get => _show_column_display_name;
set => Set(ref _show_column_display_name, value);
}
public string key => ui.spark.GridSettingsTemplateKeys.SimpleGrid;
public TSettingsData get_result() {
var animation_map = predicate_animations
.Select(p => p.create())
.GroupBy(d => d.predicate)
.ToDictionary(g => g.Key, g => animation_tuple(
g.SelectMany(v => v.animations).ToList(),
g.SelectMany(v => v.sounds).ToList()));
public IEnumerable parameters => _parameters;
IEnumerable<object> _visible_parameters {
get {
yield return _selection_highlight;
if (_background_enabled)
yield return _background;
yield return _font_vm;
yield return _font_color;
}
}
public IDisposable begin_change_columns() {
var curr_col_id = current_column?.id;
return Disposable.Create(() => {
if (curr_col_id == null || !columns.TryGetFirst(c => c.id == curr_col_id, out var curr_column))
curr_column = columns.FirstOrDefault();
_current_column.set(curr_column);
});
}
public GridSettings get_result() {
var col_settings = new List<GridColumnSettings>();
foreach (var c in columns) {
var (settings, animations) = c.create();
var settings = c.create();
col_settings.Add(settings);
foreach (IGrouping<TPredicateData, CellAnimation> g in animations)
animation_map.AddOrUpdate(g.Key, p => animation_tuple(c: g), (p, t) => t.cell_animations.AddRange(g));
}
return _create_settings_obj(new GridSettings(font_family.name(), font_size, font_weight, font_color.color.rgb_color(), selection_highlight.to_proto(), col_settings,
background: background_color.color.with((byte)(MathUtils.within_range(0, 1, background_color.opacity) * 255.0)).rgb_color()),
animation_map.Select(kv => PredicateAnimations.create_data<TRowData, TPredicateData>(kv.Key, kv.Value.cell_animations, kv.Value.row_animations, kv.Value.sounds)));
//
(List<RowAnimation> row_animations, List<SoundNotification> sounds, List<CellAnimation> cell_animations) animation_tuple(IEnumerable<RowAnimation> r = null, IEnumerable<SoundNotification> s = null, IGrouping<TPredicateData, CellAnimation> c = null) =>
(r?.ToList() ?? new List<RowAnimation>(), s?.ToList() ?? new List<SoundNotification>(), c?.ToList() ?? new List<CellAnimation>());
return new GridSettings(
_font_vm.family.name(),
_font_vm.size,
_font_vm.weight,
_font_color.value.rgb_color(),
new ColorOpacityProto(_selection_highlight.value.rgb_color()),
col_settings,
background: _background_enabled ? _background.value.rgb_color() : 0,
enable_background_color: _background_enabled);
}
public void load(TSettingsData sd) {
_font_family.set(new FontFamily(sd.general.font_family));
_font_weight.set(sd.general.font_weight);
font_color.load_from(sd.general.font_color.rgb_solid_brush());
selection_highlight.load_from(sd.general.selection_highlight.to_brush());
_show_background_color.set(sd.general.enable_background_color);
if (sd.general.enable_background_color) {
// note that when the OB is moved to allow background colors
// we can just background_color.color instead of load_from
// so that we get all the alpha goodness similar to SimpleGridSettingsVM
// assuming bind_opacity_to_alpha on the picker.
background_color.load_from(sd.general.background_color.argb_color().to_solid_brush());
}
_font_size.set(sd.general.font_size);
var col_animations = new Dictionary<string, Dictionary<TPredicateData, List<CellAnimation>>>();
predicate_animations.Clear();
foreach (var pa in sd.predicate_animations) {
if (pa.row_animations.Count > 0 || pa.sound_notifications.Count > 0) {
var vm = new PredicateRowAnimationVM(_create_predicate_vm(), _predicate_data_factory);
vm.load_from((pa.predicate_data, pa.row_animations, pa.sound_notifications));
predicate_animations.Add(vm);
}
foreach (var ca in pa.cell_animations) {
col_animations.AddOrUpdate(ca.column_id,
col => new Dictionary<TPredicateData, List<CellAnimation>> { { pa.predicate_data, new List<CellAnimation> { ca } } },
(col, d) => d.AddOrUpdate(pa.predicate_data, p => new List<CellAnimation> { ca }, (p, l) => l.Add(ca)));
public void load(GridSettings settings) =>
load(settings, true);
public void load(GridSettings settings, bool sync_columns) {
_font_vm.family = new FontFamily(settings.font_family);
_font_vm.weight = settings.font_weight;
_font_vm.size = settings.font_size;
_font_color.value = settings.font_color.rgb_color();
_selection_highlight.value = settings.selection_highlight.argb_color;
// note that when the OB is moved to allow background colors
// we can just background_color.color instead of load_from
// so that we get all the alpha goodness similar to SimpleGridSettingsVM
// assuming bind_opacity_to_alpha on the picker.
_background_enabled = settings.enable_background_color;
_background.value = settings.background;
if (sync_columns) {
using (begin_change_columns()) {
columns.SetItems(settings.columns.Select(col => {
var vm = new GridColumnSettingsVM();
vm.load_from(col);
// reapply our favorite column flags
// this comes in handy when performing Load From Factory - the editor will contain
// a consistent view of what can be overridden and what cannot
if (_column_flags.TryGetValue(vm.id, out GridColumnSettingsFeatureFlags flags))
vm.apply_flags(flags);
return vm;
}));
}
}
var curr_col_id = current_column?.id;
columns.SetItems(sd.general.columns.Select(col => {
var vm = new ColumnVM(this);
vm.load_from((col, col_animations.GetValueOrDefault(col.id)?.ToFlatLookup(kv => kv.Key, kv => kv.Value)));
// reapply our favorite column flags
// this comes in handy when performing Load From Factory - the editor will contain
// a consistent view of what can be overridden and what cannot
if (_column_flags.TryGetValue(vm.id, out GridColumnSettingsFeatureFlags flags))
vm.apply_flags(flags);
return vm;
}));
if (curr_col_id == null || !columns.TryGetFirst(c => c.id == curr_col_id, out IColumnVM curr_column))
curr_column = columns.FirstOrDefault();
_current_column.set(curr_column);
}
public class PredicateRowAnimationVM : DisposableViewModelBase, IValueFactory<(TPredicateData predicate, IEnumerable<RowAnimation> animations, IEnumerable<SoundNotification> sounds)> {
readonly Property<bool> _show_animations/*, _play_sound*/;
readonly Func<TPredicateData> _predicate_state_factory;
public PredicateRowAnimationVM(IPropertyWrapperVM<TPredicateData> predicate, Func<TPredicateData> predicate_state_factory) {
this.predicate = predicate;
_predicate_state_factory = predicate_state_factory;
_show_animations = backing(nameof(show_animations), true);
//_play_sound = backing(nameof(play_sound), true);
animations = new ObservableEditableCollection<RowAnimationVM>(() => {
var vm = new RowAnimationVM();
vm.selected_animation_type = vm.animation_types.FirstOrDefault();
return vm;
}, true);
animations.CollectionChanged += WPFHelper.create_collection_changed_cb(on_items_added: (_, __) => _show_animations.set(true));
_dispose = predicate as IDisposable;
}
public IPropertyWrapperVM<TPredicateData> predicate { get; }
public ObservableEditableCollection<RowAnimationVM> animations { get; }
public IEnumerable<CellAnimation.AnimationTypeOneofCase> animation_types { get; } = EnumHelper.get_values_except(CellAnimation.AnimationTypeOneofCase.None);
public bool show_animations { get => _show_animations.Value; set => _show_animations.set(value); }
//public bool play_sound { get => _play_sound.Value; set => _play_sound.set(value); }
public IPropertyWrapperVM<SoundNotification> sound { get; } = SoundAnimationVM.create_system_sound_vm();
public (TPredicateData predicate, IEnumerable<RowAnimation> animations, IEnumerable<SoundNotification> sounds) create() =>
(predicate.create_assigned(_predicate_state_factory),
animations.Select(a => a.create_assigned()),
new[] { sound.create_assigned() });
//EnumerableExtensions.return_if(play_sound, () => sound.create_assigned()));
public void load_from((TPredicateData predicate, IEnumerable<RowAnimation> animations, IEnumerable<SoundNotification> sounds) args) {
predicate.load_from(args.predicate);
animations.SetItems(args.animations.Select(a => {
var vm = new RowAnimationVM();
vm.load_from(a);
return vm;
}));
var en = args.sounds.GetEnumerator();
if (en.MoveNext()) {
//_play_sound.set(true);
sound.load_from(en.Current);
} //else _play_sound.set(false);
}
_parameters.sync_with(_visible_parameters);
}
}
}
File diff suppressed because it is too large. Load diff
@@ -49,6 +49,8 @@ namespace mbts.ui.controls
};
}
public bool SelectItemOnDrop { get; set; }
private DragIndicatorAdorner<DraggableListBoxItem> _adorner;
private IDisposable _drag_operation;
@@ -56,9 +58,13 @@ namespace mbts.ui.controls
protected override DependencyObject GetContainerForItemOverride() {
var container = new DraggableListBoxItem();
container.MouseEnter += (s, e) => _adorner.InvalidateVisual();
container.MouseLeave += (s, e) => _adorner.InvalidateVisual();
container.MouseEnter += on_mouse_changed;
container.MouseLeave += on_mouse_changed;
container.MouseMove += on_mouse_changed;
return container;
//
void on_mouse_changed(object sender, MouseEventArgs e) =>
_adorner.InvalidateVisual();
}
protected override void OnPreviewMouseLeftButtonDown(MouseButtonEventArgs e) {
@@ -89,6 +95,8 @@ namespace mbts.ui.controls
if (source != null && target != null) {
CoerceValue(DragDropManagerProperty);
DragDropManager.Drop(source, target.Content);
if (SelectItemOnDrop && target is ListBoxItem lbi)
lbi.SetCurrentValue(ListBoxItem.IsSelectedProperty, true);
}
}
@@ -1,6 +1,6 @@
using mbts.ui.utility;
using bts.utils;
using mbts.ui.utility;
using mbts.ui.utility.text;
using bts.utils;
using System;
using System.Collections;
using System.Globalization;
@@ -9,7 +9,6 @@ using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Media;
using FontFamily = System.Windows.Media.FontFamily;
namespace mbts.ui.controls {
/// <summary>
@@ -20,13 +19,8 @@ namespace mbts.ui.controls {
public FontPicker() {
InitializeComponent();
var families = Fonts.SystemFontFamilies.ToList();
families.Sort(CompareUtils.safe_comparison<FontFamily>(
(a, b) => a.FamilyNames.First().Value.CompareTo(b.FamilyNames.First().Value)));
SetCurrentValue(FontFamiliesProperty, families);
var weights = TextUtils.all_font_weights().ToList();
weights.Sort((x, y) => x.ToOpenTypeWeight().CompareTo(y.ToOpenTypeWeight()));
SetCurrentValue(FontWeightsProperty, weights);
SetCurrentValue(FontFamiliesProperty, TextUtils.SystemFontFamilies);
SetCurrentValue(FontWeightsProperty, TextUtils.SystemFontWeights);
Loaded += (s, e) => _font_family_list_box.ScrollIntoView(SelectedFontFamily);
}
@@ -1,8 +1,10 @@
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:c="clr-namespace:mbts.ui.controls"
xmlns:usp="clr-namespace:mbts.ui.spark"
xmlns:uspcv="clr-namespace:mbts.ui.spark.converters">
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:c="clr-namespace:mbts.ui.controls"
xmlns:usp="clr-namespace:mbts.ui.spark"
xmlns:uspcv="clr-namespace:mbts.ui.spark.converters"
>
<uspcv:OrderTypeBrushConverter x:Key="OrderTypeBrushConverter"/>
<uspcv:OrderTypeDisplayTextConverter x:Key="OrderTypeDisplayTextConverter"/>
<Style TargetType="usp:OrderTypeItem">
@@ -26,15 +28,17 @@
<ColumnDefinition Width="Auto"/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<c:Badge
<Rectangle
HorizontalAlignment="Left"
VerticalAlignment="Center"
Background="{Binding BadgeFill, RelativeSource={RelativeSource TemplatedParent}}"
BorderThickness="1"
Fill="{Binding BadgeFill, RelativeSource={RelativeSource TemplatedParent}}"
Stroke="Black"
StrokeThickness="1"
Width="15"
Height="10"
Focusable="False"
IsTabStop="False"
RadiusX="{x:Static c:Badge.DefaultCornerRadius}"
RadiusY="{x:Static c:Badge.DefaultCornerRadius}"
/>
<TextBlock
x:Name="text"
@@ -10,9 +10,18 @@ namespace mbts.ui.controls {
/// Interaction logic for PopupControl.xaml
/// </summary>
public class PopupControl : HeaderedContentControl {
IDisposable _popup_hook;
Popup _popup;
Button _button;
static PopupControl() =>
DefaultStyleKeyProperty.OverrideMetadata(typeof(PopupControl), new FrameworkPropertyMetadata(typeof(PopupControl)));
public PopupControl() {
CommandBindings.Add(new CommandBinding(OpenCommand, (s, e) => IsOpen = true));
CommandBindings.Add(new CommandBinding(CloseCommand, (s, e) => IsOpen = false));
}
public static readonly RoutedCommand
OpenCommand = new RoutedCommand("Open", typeof(PopupControl)),
CloseCommand = new RoutedCommand("Close", typeof(PopupControl));
@@ -23,6 +32,16 @@ namespace mbts.ui.controls {
public event RoutedEventHandler Opened { add => AddHandler(OpenedEvent, value); remove => RemoveHandler(OpenedEvent, value); }
public event RoutedEventHandler Closed { add => AddHandler(ClosedEvent, value); remove => RemoveHandler(ClosedEvent, value); }
public static readonly DependencyProperty ButtonStyleProperty =
WPFHelper.create_dp<Style, PopupControl>(
nameof(ButtonStyle),
(o, v) => WPFHelper.apply_style(ref o._button, v));
public Style ButtonStyle {
get => GetValue(ButtonStyleProperty) as Style;
set => SetValue(ButtonStyleProperty, value);
}
#region PopupPlacement
public static readonly DependencyProperty PopupPlacementProperty = DependencyProperty.Register("PopupPlacement", typeof(PlacementMode), typeof(PopupControl), new PropertyMetadata(PlacementMode.Right, OnPopupPlacementChanged));
private static void OnPopupPlacementChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { d.SetValue(IsOpenTransformPropertyKey, _get_is_open_transform((PlacementMode)e.NewValue)); }
@@ -105,18 +124,12 @@ namespace mbts.ui.controls {
}
#endregion
public PopupControl() {
CommandBindings.Add(new CommandBinding(OpenCommand, (s, e) => IsOpen = true));
CommandBindings.Add(new CommandBinding(CloseCommand, (s, e) => IsOpen = false));
}
IDisposable _popup_hook;
Popup _popup;
Button _button;
public override void OnApplyTemplate() {
Disposable.dispose(ref _popup_hook);
// button
_button = (Button)GetTemplateChild("button");
WPFHelper.apply_style(ref _button, ButtonStyle);
// popup
_popup = (Popup)GetTemplateChild("popup");
_popup.HorizontalOffset = HorizontalOffset;
_popup.VerticalOffset = VerticalOffset;
@@ -0,0 +1,175 @@
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:u="clr-namespace:mbts.ui"
xmlns:c="clr-namespace:mbts.ui.controls"
xmlns:usp="clr-namespace:mbts.ui.spark"
xmlns:uspcv="clr-namespace:mbts.ui.spark.converters"
xmlns:txt="clr-namespace:mbts.ui.utility.text"
>
<!--Control colors.-->
<Color x:Key="WindowColor">#FFE8EDF9</Color>
<Color x:Key="ContentAreaColorLight">#FFC5CBF9</Color>
<Color x:Key="ContentAreaColorDark">#FF7381F9</Color>
<Color x:Key="DisabledControlLightColor">#FFE8EDF9</Color>
<Color x:Key="DisabledControlDarkColor">#FFC5CBF9</Color>
<Color x:Key="DisabledForegroundColor">#FF888888</Color>
<Color x:Key="SelectedBackgroundColor">#FFC5CBF9</Color>
<Color x:Key="SelectedUnfocusedColor">#FFDDDDDD</Color>
<Color x:Key="ControlLightColor">White</Color>
<Color x:Key="ControlMediumColor">#FF7381F9</Color>
<Color x:Key="ControlDarkColor">#FF211AA9</Color>
<Color x:Key="ControlMouseOverColor">#FF3843C4</Color>
<Color x:Key="ControlPressedColor">#FF211AA9</Color>
<Color x:Key="GlyphColor">#FF444444</Color>
<Color x:Key="GlyphMouseOver">sc#1, 0.004391443, 0.002428215, 0.242281124</Color>
<!--Border colors-->
<Color x:Key="BorderLightColor">#FFCCCCCC</Color>
<Color x:Key="BorderMediumColor">#FF888888</Color>
<Color x:Key="BorderDarkColor">#FF444444</Color>
<Color x:Key="PressedBorderLightColor">#FF888888</Color>
<Color x:Key="PressedBorderDarkColor">#FF444444</Color>
<Color x:Key="DisabledBorderLightColor">#FFAAAAAA</Color>
<Color x:Key="DisabledBorderDarkColor">#FF888888</Color>
<Style x:Key="FlatTabItem" TargetType="{x:Type TabItem}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type TabItem}">
<Grid x:Name="Root">
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="SelectionStates">
<VisualState x:Name="Unselected" />
<VisualState x:Name="Selected">
<Storyboard>
<ColorAnimationUsingKeyFrames Storyboard.TargetName="Border" Storyboard.TargetProperty="(Panel.Background).(GradientBrush.GradientStops)[1].(GradientStop.Color)">
<EasingColorKeyFrame KeyTime="0" Value="{StaticResource ControlPressedColor}"/>
</ColorAnimationUsingKeyFrames>
<ThicknessAnimationUsingKeyFrames Storyboard.TargetProperty="(Border.BorderThickness)" Storyboard.TargetName="Border">
<EasingThicknessKeyFrame KeyTime="0" Value="1,1,1,0"/>
</ThicknessAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
</VisualStateGroup>
<VisualStateGroup x:Name="CommonStates">
<VisualState x:Name="Normal" />
<VisualState x:Name="MouseOver" />
<VisualState x:Name="Disabled">
<Storyboard>
<ColorAnimationUsingKeyFrames Storyboard.TargetName="Border" Storyboard.TargetProperty="(Panel.Background).(GradientBrush.GradientStops)[1].(GradientStop.Color)">
<EasingColorKeyFrame KeyTime="0" Value="{StaticResource DisabledControlDarkColor}"/>
</ColorAnimationUsingKeyFrames>
<ColorAnimationUsingKeyFrames Storyboard.TargetName="Border" Storyboard.TargetProperty="(Border.BorderBrush).(SolidColorBrush.Color)">
<EasingColorKeyFrame KeyTime="0" Value="{StaticResource DisabledBorderLightColor}"/>
</ColorAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
<Border
x:Name="Border"
Margin="0,0,-4,0"
BorderThickness="1"
>
<Border.BorderBrush>
<SolidColorBrush Color="{DynamicResource BorderMediumColor}" />
</Border.BorderBrush>
<Border.Background>
<LinearGradientBrush StartPoint="0,0" EndPoint="0,1">
<LinearGradientBrush.GradientStops>
<GradientStopCollection>
<GradientStop Color="{DynamicResource ControlLightColor}" Offset="0.0"/>
<GradientStop Color="{DynamicResource ControlMediumColor}" Offset="1.0"/>
</GradientStopCollection>
</LinearGradientBrush.GradientStops>
</LinearGradientBrush>
</Border.Background>
<ContentPresenter
x:Name="ContentSite"
VerticalAlignment="Center"
HorizontalAlignment="Center"
ContentSource="Header"
Margin="12,2,12,2"
RecognizesAccessKey="True"
/>
</Border>
</Grid>
<ControlTemplate.Triggers>
<Trigger Property="IsSelected" Value="True">
<Setter Property="Panel.ZIndex" Value="100"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<Style TargetType="{x:Type c:HorizontalTabControl}">
<Setter Property="OverridesDefaultStyle" Value="True"/>
<Setter Property="SnapsToDevicePixels" Value="True"/>
<Setter Property="ItemContainerStyle" Value="{StaticResource FlatTabItem}"/>
<Setter Property="ItemsPanel">
<Setter.Value>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal"/>
</ItemsPanelTemplate>
</Setter.Value>
</Setter>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type c:HorizontalTabControl}">
<Grid KeyboardNavigation.TabNavigation="Local">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="CommonStates">
<VisualState x:Name="Disabled">
<Storyboard>
<ColorAnimationUsingKeyFrames Storyboard.TargetName="Border" Storyboard.TargetProperty="(Border.BorderBrush).(SolidColorBrush.Color)">
<EasingColorKeyFrame KeyTime="0" Value="#FFAAAAAA"/>
</ColorAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
<ItemsPresenter
x:Name="HeaderPanel"
Grid.Row="0"
Panel.ZIndex="1"
Margin="0,0,4,-1"
KeyboardNavigation.TabIndex="1"
>
</ItemsPresenter>
<ContentPresenter
x:Name="PART_SelectedContentHost"
Grid.Row="1"
Grid.ColumnSpan="2"
KeyboardNavigation.TabNavigation="Local"
KeyboardNavigation.DirectionalNavigation="Contained"
KeyboardNavigation.TabIndex="2"
Margin="4"
ContentSource="SelectedContent"
/>
<!--<Rectangle Grid.Column="1" HorizontalAlignment="Stretch" VerticalAlignment="Top" Height="1" Fill="{TemplateBinding BorderBrush}"/>-->
<Rectangle Grid.Column="1" HorizontalAlignment="Stretch" VerticalAlignment="Bottom" Margin="-4,0,0,-1" Height="1" Fill="DarkGray"/>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>
@@ -0,0 +1,24 @@
using bts.api;
using bts.utils;
using mbts.spark;
using mbts.ui.controls;
using mbts.ui.extensions;
using mbts.ui.utilities;
using mbts.ui.utility;
using System;
using System.Globalization;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Input;
namespace mbts.ui.controls {
/// <summary>
/// Interaction logic for HorizontalTabControl.xaml
/// </summary>
class HorizontalTabControl : TabControl {
static HorizontalTabControl() =>
DefaultStyleKeyProperty.OverrideMetadata(typeof(HorizontalTabControl), new FrameworkPropertyMetadata(typeof(HorizontalTabControl)));
}
}
@@ -1,7 +1,10 @@
using bts.spark;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Windows.Data;
using System.Windows.Media;
namespace mbts.ui.converters {
static class ValueConverter {
@@ -25,8 +28,12 @@ namespace mbts.ui.converters {
public static readonly IValueConverter
DoubleTextConverter = new DoubleTextConverter(),
DoubleGreaterThanZero = new DoubleGreaterThanConverter { Value = 0 },
GeometryParser = new TypeConverterWrapper {
TypeConverter = new GeometryConverter()
},
IntTextConverter = new IntTextConverter(),
UIntTextConverter = new UIntTextConverter(),
NotNull = ValueConverter.create(obj => obj != null),
QuantityMeasureTextConverter = ValueConverter.create<QuantityMeasureImmutable?, string>(fmt),
SecuritySelectorText,
SimpleString = new AnonymousValueConverter(obj => obj?.ToString()),
@@ -40,7 +47,12 @@ namespace mbts.ui.converters {
}
public static readonly IMultiValueConverter
AreEqualConverter = new AreEqualConverter();
AreEqualConverter = new AreEqualConverter(),
SliderPercentage = ValueConverter.create<double, double, double, double>((value, min, max) =>
DoubleHelper.AnyNaN(value, min, max) || min >= max ?
double.NaN :
(value - min) / (max - min)),
Any = new AnonymousMultiValueConverter(objs => objs.OfTypeSafe<bool>().Any(_ => _), null);
public static string fmt(QuantityMeasureImmutable? qm) {
if (qm.HasValue) {
@@ -80,6 +92,22 @@ namespace mbts.ui.converters {
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) { return _convert_back(ConvertUtils.try_convert<TOut>(value)); }
}
class AnonymousMultiValueConverter : IMultiValueConverter {
readonly Func<object[], object> _convert;
readonly Func<object, object[]> _convert_back;
public AnonymousMultiValueConverter(Func<object[], object> convert, Func<object, object[]> convert_back) {
_convert = convert;
_convert_back = convert_back;
}
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture) =>
_convert(values);
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture) =>
_convert_back == null ? throw new InvalidOperationException() : _convert_back(value);
}
public class AnonymousMultiValueConverter<TIn1, TIn2, TOut> : IMultiValueConverter {
readonly Func<TIn1, TIn2, TOut> _convert;
@@ -0,0 +1,92 @@
using mbts.utils;
using System;
using System.Collections;
using System.Globalization;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
namespace mbts.ui.converters {
class ComparisonConverter : IValueConverter {
public object ValueForGreaterThan { get; set; } = 1;
public object ValueForLessThan { get; set; } = -1;
public object ValueForEqual { get; set; }
public object Comparand { get; set; }
public IComparer Comparer { get; set; }
public object Convert(object value, Type targetType, object parameter, CultureInfo culture) =>
convert(value, Comparand, Comparer, ValueForLessThan, ValueForGreaterThan, ValueForEqual);
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) =>
convert(value, Comparand, Comparer, ValueForGreaterThan, ValueForLessThan, ValueForEqual);
public static object convert(object value, object comparand, IComparer comparer, object value_for_less_than, object value_for_greater_than, object value_for_equal_to) {
int result = compare(value, comparand, comparer);
if (result < 0)
return value_for_less_than;
if (result > 0)
return value_for_greater_than;
return value_for_equal_to;
}
public static int compare(object value, object comparand, IComparer comparer) {
if (comparer != null) {
return comparer.Compare(value, comparand);
} else if (value is IComparable comparable) {
return comparable.CompareTo(comparand);
} else {
return System.Collections.Comparer.Default.Compare(value, comparand);
}
}
}
class IsLessThanConverter : ComparisonConverter {
public IsLessThanConverter() {
ValueForGreaterThan = false;
ValueForEqual = false;
ValueForLessThan = true;
}
}
class IsGreaterThanConverter : ComparisonConverter {
public IsGreaterThanConverter() {
ValueForLessThan = false;
ValueForEqual = false;
ValueForGreaterThan = true;
}
}
class DynamicComparisonConverter : IMultiValueConverter {
public object ValueForGreaterThan { get; set; } = 1;
public object ValueForLessThan { get; set; } = -1;
public object ValueForEqualTo { get; set; }
public IComparer Comparer { get; set; }
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture) {
ExceptionAssert.array_length(values, 2, nameof(values));
if (values.Contains(DependencyProperty.UnsetValue))
return new ValidationResult(false, null);
return ComparisonConverter.convert(values[0], values[1], Comparer, ValueForLessThan, ValueForGreaterThan, ValueForEqualTo);
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture) =>
throw new NotSupportedException();
}
class DynamicIsLessThanConverter : DynamicComparisonConverter {
public DynamicIsLessThanConverter() {
ValueForLessThan = true;
ValueForEqualTo = true;
ValueForGreaterThan = false;
}
}
class DynamicIsGreaterThanConverter : DynamicComparisonConverter {
public DynamicIsGreaterThanConverter() {
ValueForLessThan = false;
ValueForEqualTo = false;
ValueForGreaterThan = true;
}
}
}
@@ -0,0 +1,46 @@
using bts.utils;
using mbts.ui.utility.text;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Text;
using System.Windows;
using System.Windows.Data;
namespace mbts.ui.converters {
class FontWeightDisplayTextConverter : IValueConverter {
// TODO (when there reallllly isn't anything else to do):
// come up with interpolation naming system (e.g., if weight is between 'Light' and 'Super Light,' name it 'Semi Light');
// below is the beginning of such an effort.
//static SortedList<FontWeight, string> _font_weight_cache =
// new SortedList<FontWeight, string>(Comparer<int>.Default.wrap<int, FontWeight>(fw => fw.ToOpenTypeWeight()));
//static FontWeightDisplayTextConverter() {
// foreach (var fw in TextUtils.SystemFontWeights)
// _font_weight_cache.Add(fw, fw.ToString());
//}
public object Convert(object value, Type targetType, object parameter, CultureInfo culture) {
if (value is FontWeight fw) {
string rv = fw.ToString();
if (int.TryParse(rv, out var open_type_weight)) {
// msoft doesn't have a pretty name
rv = _fallback_name(open_type_weight);
}
return rv;
}
return null;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) =>
throw new NotImplementedException();
static string _fallback_name(int open_type_weight) {
switch (open_type_weight) {
case 350: return "Semi Light";
default: return open_type_weight.ToString();
}
}
}
}
@@ -0,0 +1,16 @@
using System;
using System.ComponentModel;
using System.Globalization;
using System.Windows.Data;
namespace mbts.ui.converters {
class TypeConverterWrapper : IValueConverter {
public TypeConverter TypeConverter { get; set; }
public object Convert(object value, Type targetType, object parameter, CultureInfo culture) =>
TypeConverter.ConvertFrom(null, culture, value);
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) =>
TypeConverter.ConvertTo(null, culture, value, targetType);
}
}
@@ -13,12 +13,21 @@ namespace mbts.ui.converters {
public IList Map => _map;
public object Default { get; set; }
public IValueConverter DefaultConverter { get; set; }
public object Convert(object value, Type targetType, object parameter, CultureInfo culture) =>
_map.TryGetValue(value ?? KeyValue.NullPlaceholder, out var kv) ? kv.Value : Default;
_map.TryGetValue(value ?? KeyValue.NullPlaceholder, out var kv) ?
kv.Value :
DefaultConverter == null ?
Default :
DefaultConverter.Convert(value, targetType, parameter, culture);
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) =>
_map.TryGetFirst(kv => Equals(kv.Value, value), out KeyValue result) ? result.Key : Default;
_map.TryGetFirst(kv => Equals(kv.Value, value), out KeyValue result) ?
result.Key :
DefaultConverter == null ?
Default :
DefaultConverter.ConvertBack(value, targetType, parameter, culture);
}
class KeyValue {
@@ -1,10 +1,14 @@
using mbts.config;
using mbts.ui.controls;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Windows;
using System.Windows.Forms;
using System.Windows.Media;
using Point = System.Windows.Point;
namespace mbts.ui.dpi {
public static class HighDpiExtensions {
@@ -119,5 +123,46 @@ namespace mbts.ui.dpi {
}
}
#endregion
/// <summary>
/// Calculates the value to be used for layout rounding at high DPI.
/// </summary>
/// <param name="value">Input value to be rounded.</param>
/// <param name="dpiScale">Ratio of screen's DPI to layout DPI</param>
/// <returns>Adjusted value that will produce layout rounding on screen at high dpi.</returns>
/// <remarks>This is a layout helper method. It takes DPI into account and also does not return
/// the rounded value if it is unacceptable for layout, e.g. Infinity or NaN. It's a helper associated with
/// UseLayoutRounding property and should not be used as a general rounding utility.</remarks>
public static double RoundLayoutValue(this double value) =>
RoundLayoutValue(value, dpi_ratio);
public static double RoundLayoutValue(double value, double dpiScale) {
if (DoubleUtil.AreClose(dpiScale, 1.0))
return Math.Round(value);
double result = Math.Round(value * dpiScale) / dpiScale;
return DoubleHelper.IsNaNOrInfinity(result) || DoubleUtil.AreClose(result, double.MaxValue) ? value : result;
}
public static Point RoundToLayout(this Point point) =>
new Point(RoundLayoutValue(point.X), RoundLayoutValue(point.Y));
public static Thickness RoundToLayout(this Thickness thickness) =>
new Thickness(
thickness.Left.RoundLayoutValue(),
thickness.Top.RoundLayoutValue(),
thickness.Right.RoundLayoutValue(),
thickness.Bottom.RoundLayoutValue());
public static GuidelineSet AntiAliasGuidelines(this Rect rect) =>
rect.AntiAliasGuidelines(dpi_ratio);
public static GuidelineSet AntiAliasGuidelines(this Rect rect, double pen_thickness) {
var half_pen_thickness = pen_thickness / 2;
//half_pen_thickness = 0;
var guidelines = new GuidelineSet(
new[] { rect.Left.RoundLayoutValue() - half_pen_thickness, rect.Right.RoundLayoutValue() + half_pen_thickness },
new[] { rect.Top.RoundLayoutValue() - half_pen_thickness, rect.Bottom.RoundLayoutValue() + half_pen_thickness });
return guidelines;
}
}
}
@@ -14,6 +14,15 @@ namespace mbts.ui {
CommitAndRelease
}
// RoutedEventHandlers
public static RoutedEventHandler FocusTextBoxRoutedEventHandler => _focus_text_box_routed_event_handler;
static void _focus_text_box_routed_event_handler(object sender, RoutedEventArgs e) {
if (sender is TextBox tb || sender is FrameworkElement el && el.try_find_descendent_of_type(out tb)) {
e.Handled = true;
Keyboard.Focus(tb);
}
}
#region RoutedCommands/Handlers
public static readonly RoutedCommand
ReleaseCaptureCommand = new RoutedCommand(),
+287 -7
View File
@@ -1,10 +1,11 @@
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mbts="clr-namespace:mbts.ui"
xmlns:u="clr-namespace:mbts.ui"
xmlns:controls="clr-namespace:mbts.ui.controls">
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mbts="clr-namespace:mbts.ui"
xmlns:u="clr-namespace:mbts.ui"
xmlns:controls="clr-namespace:mbts.ui.controls"
xmlns:cv="clr-namespace:mbts.ui.converters"
>
<SolidColorBrush x:Key="StartMenuMouseOverBorderBrush" Color="#FFFFB700" />
<SolidColorBrush x:Key="StartMenuMouseOverBackground" Color="#6ddbff" />
<SolidColorBrush x:Key="StartMenuPressedBorderBrush" Color="#FFC29B29" />
@@ -22,6 +23,38 @@
<SolidColorBrush x:Key="Button.MousePressed.BackgroundBrush" Color="{StaticResource Button.MousePressed.BackgroundColor}" />
<SolidColorBrush x:Key="Button.MousePressed.BorderBrush" Color="{StaticResource Button.MousePressed.BorderColor}" />
<!--Storyboards/Timelines-->
<!--Button Mouse Over-->
<ObjectAnimationUsingKeyFrames x:Key="Button.MouseOver.BackgroundTimeline" Storyboard.TargetProperty="Background">
<DiscreteObjectKeyFrame KeyTime="0" Value="{StaticResource Control.LightMediumGrayBrush}"/>
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames x:Key="Button.MouseOver.BorderTimeline" Storyboard.TargetProperty="BorderBrush">
<DiscreteObjectKeyFrame KeyTime="0" Value="{mbts:BTSColor UI.Button.mouseover_background}"/>
</ObjectAnimationUsingKeyFrames>
<Storyboard x:Key="Button.MouseOver.Storyboard">
<StaticResource ResourceKey="Button.MouseOver.BackgroundTimeline"/>
<StaticResource ResourceKey="Button.MouseOver.BorderTimeline"/>
</Storyboard>
<!--Button Pressed-->
<ObjectAnimationUsingKeyFrames x:Key="Button.Pressed.BackgroundTimeline" Storyboard.TargetProperty="Background">
<DiscreteObjectKeyFrame KeyTime="0" Value="{mbts:BTSColor UI.Button.pressed_background}"/>
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames x:Key="Button.Pressed.BorderTimeline" Storyboard.TargetProperty="BorderBrush">
<DiscreteObjectKeyFrame KeyTime="0" Value="{mbts:BTSColor UI.Button.pressed_background}"/>
</ObjectAnimationUsingKeyFrames>
<Storyboard x:Key="Button.Pressed.Storyboard">
<StaticResource ResourceKey="Button.Pressed.BackgroundTimeline"/>
<StaticResource ResourceKey="Button.Pressed.BorderTimeline"/>
</Storyboard>
<!--ListBox MouseOver-->
<ObjectAnimationUsingKeyFrames x:Key="ListBoxItem.MouseOver.BackgroundTimeline" Storyboard.TargetProperty="(Control.Background)">
<DiscreteObjectKeyFrame KeyTime="0" Value="{StaticResource ListBoxItem.MouseOver.Background}"/>
</ObjectAnimationUsingKeyFrames>
<Storyboard x:Key="ListBoxItem.MouseOver.BackgroundStoryboard">
<StaticResource ResourceKey="ListBoxItem.MouseOver.BackgroundTimeline"/>
</Storyboard>
<Style x:Key="StartMenuButton" TargetType="{x:Type Button}">
<Setter Property="Background" Value="Transparent" />
<Setter Property="OverridesDefaultStyle" Value="True" />
@@ -237,7 +270,6 @@
</Setter>
</Style>
<!-- The Expander used in spark menus (Tool Bar) -->
<Style x:Key="MenuExpanderButtonBase" TargetType="ToggleButton">
<Setter Property="Background" Value="{mbts:BTSColor UI.spark_menu_background}"/>
@@ -459,6 +491,35 @@
</Setter>
</Style>
<ControlTemplate x:Key="HelpButtonGenericTemplate" TargetType="{x:Type Control}">
<Grid Width="{TemplateBinding Width}" Height="{TemplateBinding Height}">
<Grid.ToolTip>
<ToolTip Content="{TemplateBinding ToolTip}" Style="{DynamicResource HelpButtonToolTipStyleKey}"/>
</Grid.ToolTip>
<Ellipse HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Fill="{TemplateBinding Background}" Opacity="0" x:Name="_el" />
<TextBlock Text="?" HorizontalAlignment="Center" VerticalAlignment="Center" />
</Grid>
<ControlTemplate.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Foreground" Value="White" />
<Trigger.EnterActions>
<BeginStoryboard>
<Storyboard>
<DoubleAnimation Storyboard.TargetName="_el" Storyboard.TargetProperty="Opacity" To="1" Duration="0:0:0.05" />
</Storyboard>
</BeginStoryboard>
</Trigger.EnterActions>
<Trigger.ExitActions>
<BeginStoryboard>
<Storyboard>
<DoubleAnimation Storyboard.TargetName="_el" Storyboard.TargetProperty="Opacity" To="0" Duration="0:0:0.2" />
</Storyboard>
</BeginStoryboard>
</Trigger.ExitActions>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
<Style TargetType="Button" BasedOn="{StaticResource RoundMinimizeButton}" x:Key="HelpButtonBaseStyle">
<Setter Property="Background" Value="{x:Static mbts:BTSColors.yellow_brush}" />
<Setter Property="ToolTip" Value="Click here for help" />
@@ -595,6 +656,225 @@
</Style.Triggers>
</Style>
<Style x:Key="LargeMenuControlBase" TargetType="Control">
<Setter Property="Background" Value="#F5F5F5"/>
<Setter Property="BorderThickness" Value="0"/>
<Setter Property="HorizontalAlignment" Value="Stretch"/>
<Setter Property="HorizontalContentAlignment" Value="Left"/>
<Setter Property="Padding" Value="6"/>
</Style>
<Style x:Key="LargeMenuButton" TargetType="Button" BasedOn="{StaticResource LargeMenuControlBase}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Button">
<Border
x:Name="Border"
CornerRadius="0"
BorderThickness="{TemplateBinding BorderThickness}"
BorderBrush="{TemplateBinding BorderBrush}"
Background="{TemplateBinding Background}"
Padding="{TemplateBinding Padding}"
Height="{TemplateBinding Height}"
Width="{TemplateBinding Width}"
SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"
>
<u:StateManager.Groups>
<u:StateGroupsManager AttachToMembers="True">
<u:StateDefinitionCollection>
<u:MouseStateDefinition MouseSource="{Binding Group.Target.TemplatedParent, RelativeSource={RelativeSource Self}}"/>
</u:StateDefinitionCollection>
</u:StateGroupsManager>
</u:StateManager.Groups>
<VisualStateManager.VisualStateGroups>
<VisualStateGroup Name="MouseStates">
<VisualState Name="Normal"/>
<VisualState Name="MouseOver" Storyboard="{StaticResource Button.MouseOver.Storyboard}"/>
<VisualState Name="Pressed" Storyboard="{StaticResource Button.Pressed.Storyboard}"/>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
<ContentPresenter
Name="presenter"
Grid.Column="1"
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
RecognizesAccessKey="True"
/>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<Style x:Key="LargeMenuPopupControl" TargetType="controls:PopupControl">
<Setter Property="BorderThickness" Value="0"/>
<Setter Property="HorizontalAlignment" Value="Stretch"/>
<Setter Property="HorizontalContentAlignment" Value="Stretch"/>
<Setter Property="VerticalContentAlignment" Value="Stretch"/>
<Setter Property="Background" Value="Transparent"/>
<Setter Property="ButtonStyle" Value="{StaticResource LargeMenuButton}"/>
<Setter Property="ShowExpanderArrow" Value="False"/>
</Style>
<Style x:Key="AddItemButton" TargetType="Button" BasedOn="{StaticResource LargeMenuButton}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Button">
<Border
x:Name="Border"
CornerRadius="0"
BorderThickness="{TemplateBinding BorderThickness}"
BorderBrush="{TemplateBinding BorderBrush}"
Background="{TemplateBinding Background}"
Padding="{TemplateBinding Padding}"
Height="{TemplateBinding Height}"
Width="{TemplateBinding Width}"
SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"
>
<u:StateManager.Groups>
<u:StateGroupsManager AttachToMembers="True">
<u:StateDefinitionCollection>
<u:MouseStateDefinition MouseSource="{Binding Group.Target.TemplatedParent, RelativeSource={RelativeSource Self}}"/>
</u:StateDefinitionCollection>
</u:StateGroupsManager>
</u:StateManager.Groups>
<VisualStateManager.VisualStateGroups>
<VisualStateGroup Name="MouseStates">
<VisualState Name="Normal"/>
<VisualState Name="MouseOver" Storyboard="{StaticResource Button.MouseOver.Storyboard}"/>
<VisualState Name="Pressed" Storyboard="{StaticResource Button.Pressed.Storyboard}"/>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Image Height="20" Source="{x:Static u:Icons.AddBox}"/>
<ContentPresenter
Name="presenter"
Grid.Column="1"
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
Margin="8,0,0,0"
RecognizesAccessKey="True"
/>
</Grid>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<ControlTemplate x:Key="LargeMenuListBoxItemTemplate" TargetType="ListBoxItem">
<Border
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
HorizontalAlignment="{TemplateBinding HorizontalAlignment}"
VerticalAlignment="{TemplateBinding VerticalAlignment}"
Padding="{TemplateBinding Padding}"
Height="{TemplateBinding Height}"
Width="{TemplateBinding Width}"
SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"
>
<u:StateManager.Groups>
<u:StateGroupsManager AttachToMembers="True">
<u:StateDefinitionCollection>
<u:MouseStateDefinition MouseSource="{Binding Group.Target.TemplatedParent, RelativeSource={RelativeSource Self}}"/>
</u:StateDefinitionCollection>
</u:StateGroupsManager>
</u:StateManager.Groups>
<VisualStateManager.VisualStateGroups>
<VisualStateGroup Name="MouseStates">
<VisualState Name="Normal"/>
<VisualState Name="MouseOver" Storyboard="{StaticResource Button.MouseOver.Storyboard}"/>
<VisualState Name="Pressed" Storyboard="{StaticResource Button.Pressed.Storyboard}"/>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
<ContentPresenter
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
/>
</Border>
</ControlTemplate>
<DataTemplate x:Key="CheckableListBoxItemContentTemplate">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Path
VerticalAlignment="Bottom"
Margin="4,0,6,2"
Fill="Black"
Data="{Binding Source={StaticResource {x:Static u:Icons.CheckMarkGeometryKey}}, Converter={x:Static cv:ValueConverters.GeometryParser}}"
>
<Path.Visibility>
<Binding Path="IsSelected" RelativeSource="{RelativeSource AncestorType=ListBoxItem}">
<Binding.Converter>
<cv:BooleanToObjectConverter ValueForFalse="{x:Static Visibility.Hidden}" ValueForTrue="{x:Static Visibility.Visible}"/>
</Binding.Converter>
</Binding>
</Path.Visibility>
</Path>
<ContentControl Grid.Column="1" Content="{Binding}"/>
</Grid>
</DataTemplate>
<Style x:Key="LargeMenuListBoxItemBase" TargetType="ListBoxItem">
<Setter Property="HorizontalAlignment" Value="Stretch"/>
<Setter Property="HorizontalContentAlignment" Value="Left"/>
<Setter Property="VerticalAlignment" Value="Stretch"/>
<Setter Property="VerticalContentAlignment" Value="Center"/>
<Setter Property="BorderThickness" Value="0"/>
<Setter Property="Background">
<Setter.Value>
<SolidColorBrush Color="Transparent"/>
</Setter.Value>
</Setter>
<Setter Property="Padding" Value="6"/>
<Setter Property="Template" Value="{StaticResource LargeMenuListBoxItemTemplate}"/>
</Style>
<Style x:Key="LargeMenuListBoxItem" TargetType="ListBoxItem" BasedOn="{StaticResource LargeMenuListBoxItemBase}">
<Setter Property="ContentTemplate" Value="{StaticResource CheckableListBoxItemContentTemplate}"/>
</Style>
<Style x:Key="LargeMenuListBox" TargetType="ListBox">
<Setter Property="HorizontalAlignment" Value="Stretch"/>
<Setter Property="ItemContainerStyle" Value="{StaticResource LargeMenuListBoxItem}"/>
<Setter Property="BorderThickness" Value="0"/>
<Setter Property="Background">
<Setter.Value>
<SolidColorBrush Color="Transparent"/>
</Setter.Value>
</Setter>
</Style>
<ControlTemplate x:Key="NakedListBoxTemplate" TargetType="ListBox">
<ItemsPresenter/>
</ControlTemplate>
<ControlTemplate x:Key="NakedListBoxItemTemplate" TargetType="ListBoxItem">
<Border HorizontalAlignment="Stretch" VerticalAlignment="Stretch" BorderThickness="0">
<Border.Background>
<SolidColorBrush Color="Transparent"/>
</Border.Background>
<ContentPresenter/>
</Border>
</ControlTemplate>
<Style x:Key="NakedListBoxItem" TargetType="ListBoxItem">
<Setter Property="Template" Value="{StaticResource NakedListBoxItemTemplate}"/>
</Style>
<Style x:Key="NakedListBox" TargetType="ListBox">
<Setter Property="Template" Value="{StaticResource NakedListBoxTemplate}"/>
<Setter Property="ItemContainerStyle" Value="{StaticResource NakedListBoxItem}"/>
</Style>
<Style x:Key="CommonButton" BasedOn="{StaticResource {x:Type Button}}" TargetType="{x:Type Button}">
<Setter Property="MinWidth" Value="60" />
<Setter Property="Margin" Value="4" />
@@ -1,10 +1,13 @@
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:uc="clr-namespace:mbts.ui"
xmlns:u="clr-namespace:mbts.ui"
xmlns:cv="clr-namespace:mbts.ui.converters"
xmlns:uspcv="clr-namespace:mbts.ui.spark.converters"
xmlns:sys="clr-namespace:System;assembly=mscorlib">
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:uc="clr-namespace:mbts.ui"
xmlns:u="clr-namespace:mbts.ui"
xmlns:cv="clr-namespace:mbts.ui.converters"
xmlns:uspcv="clr-namespace:mbts.ui.spark.converters"
xmlns:ut="clr-namespace:mbts.ui.utilities"
xmlns:sys="clr-namespace:System;assembly=mscorlib"
>
<cv:BooleanOrConverter x:Key="OrConverter" />
<cv:BooleanAndConverter x:Key="AndConverter" />
<cv:NotConverter x:Key="NotConverter" />
@@ -25,6 +28,10 @@
<cv:EnumDescriptionConverter x:Key="EnumDescriptionConverter" />
<cv:EnumFlagsDescriptionConverter x:Key="EnumFlagsDescriptionConverter"/>
<cv:EnumerableDoubleStringConverter x:Key="EnumerableDoublesConverter" />
<cv:ValueConverterPipe x:Key="TitleCaseConverter">
<x:Static Member="CamelCaseSpacesConverter" MemberType="{x:Type ut:DisplayNameHelper}"/>
<x:Static Member="SnakeToTitleCaseConverter" MemberType="{x:Type ut:DisplayNameHelper}"/>
</cv:ValueConverterPipe>
<cv:CellPathConverter x:Key="CellPathConverter" />
<cv:CellKeyConverter x:Key="CellKeyConverter" />
<cv:SecurityDisplayTextConverter x:Key="SecurityTextConverter"/>
@@ -21,6 +21,9 @@
<DataTemplate x:Key="EnumFlagsDescriptionTemplate">
<TextBlock Text="{Binding Converter={StaticResource EnumFlagsDescriptionConverter}}" />
</DataTemplate>
<DataTemplate x:Key="TitleCaseTemplate">
<TextBlock Text="{Binding Converter={StaticResource TitleCaseConverter}}"/>
</DataTemplate>
<ItemsPanelTemplate x:Key="HorizontalItemsPanelTemplate">
<StackPanel Orientation="Horizontal"/>
</ItemsPanelTemplate>
@@ -150,6 +150,8 @@
<Color x:Key="HeaderBackgroundBorderColor">#FFAAD1FF</Color>
<Style x:Key="BTSDialog" TargetType="{x:Type Window}" BasedOn="{StaticResource BTSWindow}">
<Setter Property="AllowsTransparency" Value="True"/>
<!--<Setter Property="Background" Value="Transparent"/>-->
<Setter Property="MinHeight" Value="100" />
<Setter Property="MinWidth" Value="100" />
<Setter Property="Template">
@@ -162,6 +164,11 @@
<RowDefinition Height="1" />
<RowDefinition />
</Grid.RowDefinitions>
<Rectangle Grid.Row="0" HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
<Rectangle.Fill>
<SolidColorBrush Color="#01000000"/>
</Rectangle.Fill>
</Rectangle>
<StackPanel HorizontalAlignment="Left" VerticalAlignment="Stretch" Orientation="Horizontal">
<Image SnapsToDevicePixels="True" Height="14" Margin="4,2" VerticalAlignment="Center" HorizontalAlignment="Left" Stretch="Uniform" Source="{TemplateBinding Icon}" RenderOptions.BitmapScalingMode="HighQuality" />
<TextBlock Text="{TemplateBinding Title}" VerticalAlignment="Center" HorizontalAlignment="Stretch" x:Name="_title" />
@@ -21,7 +21,7 @@
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<usp:GlobalAlertsView/>
<c:DialogSkirt Grid.Row="1" Padding="{StaticResource DialogSkirtPadding}">
<c:DialogSkirt Grid.Row="1" Padding="{StaticResource DialogSkirtPadding}" BorderThickness="0">
<c:DialogSkirt.RightItems>
<x:Array Type="{x:Type Button}">
<Button Margin="{StaticResource DialogSkirtButtonMargin}" Command="{Binding apply_cmd}">
@@ -10,6 +10,7 @@
xmlns:cv="clr-namespace:mbts.ui.converters"
xmlns:stg="clr-namespace:mbts.ui.settings"
xmlns:usp="clr-namespace:mbts.ui.spark"
xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
xmlns:sys="clr-namespace:System;assembly=mscorlib"
>
<Grid>
@@ -21,7 +22,7 @@
<sys:Double x:Key="SecurityWidth">130</sys:Double>
<sys:Double x:Key="DoublePredicateWidth">210</sys:Double>
<sys:Double x:Key="SideWidth">100</sys:Double>
<sys:Double x:Key="SoundWidth">163</sys:Double>
<sys:Double x:Key="SoundWidth">130</sys:Double>
<Thickness x:Key="CellPadding">10,8</Thickness>
<!--Converters-->
<cv:AppendTextConverter x:Key="DropDownSharedSizeGroupConverter" Suffix="_DropDownWidth"/>
@@ -195,26 +196,21 @@
SelectedItem="{Binding predicate.side}"
ItemTemplate="{StaticResource EnumFlagsDescriptionTemplate}"
/>
<Grid c:DIYGrid.ColumnKey="Sound" Margin="{StaticResource CellPadding}">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<ComboBox
ItemsSource="{Binding sound.system_sound_options}"
SelectedItem="{Binding sound.system_sound}"
ItemTemplate="{StaticResource EnumDescriptionTemplate}"
HorizontalAlignment="Stretch"
/>
<Button
Grid.Column="1"
Margin="4,0,0,0"
HorizontalAlignment="Left"
Command="{x:Static c:SoundAnimationView.PreviewSoundCommand}"
CommandParameter="{Binding sound.system_sound}"
Style="{StaticResource RoundPlayButton}"
/>
</Grid>
<ComboBox
x:Name="sound_dropdown"
c:DIYGrid.ColumnKey="Sound"
Margin="{StaticResource CellPadding}"
ItemsSource="{Binding sound.system_sound_options}"
SelectedItem="{Binding sound.system_sound}"
ItemTemplate="{StaticResource EnumDescriptionTemplate}"
HorizontalAlignment="Stretch"
>
<i:Interaction.Triggers>
<i:EventTrigger EventName="DropDownClosed">
<i:InvokeCommandAction Command="{x:Static c:SoundAnimationView.PreviewSoundCommand}" CommandParameter="{Binding SelectedItem, ElementName=sound_dropdown}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</ComboBox>
<Rectangle Grid.Column="0" HorizontalAlignment="Right" Width="1" Fill="Gray"/>
<Rectangle Grid.Column="1" HorizontalAlignment="Right" Width="1" Fill="Gray"/>
<Rectangle Grid.Column="2" HorizontalAlignment="Right" Width="1" Fill="Gray"/>
@@ -225,14 +221,11 @@
</DataTemplate>
</c:DIYGrid.RowTemplate>
</c:DIYGrid>
<TextBlock Grid.Row="1" HorizontalAlignment="Left" Margin="4,8">
<Hyperlink Command="{Binding add_alert_cmd}">
Click to Add Alert
</Hyperlink>
</TextBlock>
<Button Grid.Row="1" HorizontalAlignment="Stretch" HorizontalContentAlignment="Center" VerticalAlignment="Top" VerticalContentAlignment="Center" Command="{Binding add_alert_cmd}">
<TextBlock Margin="8,4" FontWeight="SemiBold">Add Alert</TextBlock>
</Button>
</Grid>
</DataTemplate>
<!--<DataTemplate DataType-->
</ResourceDictionary>
</Grid.Resources>
<Grid.RowDefinitions>
@@ -17,6 +17,7 @@
Title="{Binding title, StringFormat='{}{0} Settings'}"
Icon="{x:Static u:Icons.Admin}"
Style="{StaticResource BTSDialog}"
UseLayoutRounding="True"
>
<Window.Resources>
<!--Primitive Constants-->
@@ -34,6 +35,30 @@
<cv:KeyValue Key="{u:True}" Value="{u:FontWeight Bold}"/>
</cv:ValueMap>
<!--Styles (Shared/Common)-->
<DataTemplate x:Key="FontPickerTemplate">
<c:FontPicker SelectedFontFamily="{Binding font_family}" SelectedFontSize="{Binding font_size}" SelectedFontWeight="{Binding font_weight}"/>
</DataTemplate>
<DataTemplate x:Key="FontDisplayTemplate">
<TextBlock FontFamily="{Binding font_family}" FontSize="{Binding font_size}" FontWeight="{Binding font_weight}">
<TextBlock.Text>
<MultiBinding StringFormat="{}{0}, {1:#0.##} pt">
<Binding Path="font_family"/>
<Binding Path="font_size"/>
</MultiBinding>
</TextBlock.Text>
</TextBlock>
</DataTemplate>
<DataTemplate x:Key="FontFamilyAndWeightDisplayTemplate">
<TextBlock FontFamily="{Binding font_family}" FontSize="{Binding font_size}" FontWeight="{Binding font_weight}">
<TextBlock.Text>
<MultiBinding StringFormat="{}{0}, {1}, {2:#0.##} pt">
<Binding Path="font_family"/>
<Binding Path="font_weight"/>
<Binding Path="font_size"/>
</MultiBinding>
</TextBlock.Text>
</TextBlock>
</DataTemplate>
<Style x:Key="FontPickerPopupControl" TargetType="c:PopupControl">
<Setter Property="Background" Value="{StaticResource ComboBox.Static.Background}"/>
<Setter Property="HorizontalAlignment" Value="Stretch"/>
@@ -41,27 +66,8 @@
<Setter Property="MinHeight" Value="24"/>
<Setter Property="Header" Value="{Binding}"/>
<Setter Property="Content" Value="{Binding}"/>
<Setter Property="HeaderTemplate">
<Setter.Value>
<DataTemplate>
<TextBlock FontFamily="{Binding font_family}" FontSize="{Binding font_size}" FontWeight="{Binding font_weight}">
<TextBlock.Text>
<MultiBinding StringFormat="{}{0}, {1:#0.##} pt">
<Binding Path="font_family"/>
<Binding Path="font_size"/>
</MultiBinding>
</TextBlock.Text>
</TextBlock>
</DataTemplate>
</Setter.Value>
</Setter>
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<c:FontPicker SelectedFontFamily="{Binding font_family}" SelectedFontSize="{Binding font_size}" SelectedFontWeight="{Binding font_weight}"/>
</DataTemplate>
</Setter.Value>
</Setter>
<Setter Property="ContentTemplate" Value="{StaticResource FontPickerTemplate}"/>
<Setter Property="HeaderTemplate" Value="{StaticResource FontDisplayTemplate}"/>
</Style>
<!--DataTemplates-->
<DataTemplate x:Key="GridSectionCoreContentTemplate">
@@ -240,6 +246,7 @@
</Style>
</u:StyleSelectorKeyValue>
</u:KeyedStyleSelector>
<DataTemplate x:Key="AnchorItemContentTemplate">
<Border Padding="4">
<Grid>
@@ -332,19 +339,11 @@
<u:KeyedTemplateSelector x:Key="GridSettingsTemplateSelector">
<u:TemplateSelectorKeyValue Key="{x:Static usp:GridSettingsTemplateKeys.SimpleGrid}">
<DataTemplate>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<c:ActionMenu HorizontalAlignment="Right" Margin="0,2,0,0" DataContext="{Binding DataContext, RelativeSource={RelativeSource AncestorType=usp:ShellSettingsDialog}}" ItemsSource="{Binding action_menu_items}"/>
<usp:GridSettingsView
Grid.Row="1"
GridSectionContent="{Binding}"
GridSectionContentTemplate="{StaticResource GridSectionCoreContentTemplate}"
CellContentTemplate="{DynamicResource DefaultCellContentTemplate}"
/>
</Grid>
<usp:GridSettingsView
GridSectionContent="{Binding}"
GridSectionContentTemplate="{StaticResource GridSectionCoreContentTemplate}"
CellContentTemplate="{DynamicResource DefaultCellContentTemplate}"
/>
</DataTemplate>
</u:TemplateSelectorKeyValue>
<u:TemplateSelectorKeyValue Key="{x:Static usp:GridSettingsTemplateKeys.Ladder}">
@@ -374,6 +373,7 @@
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<Border Grid.ColumnSpan="4" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Background="{StaticResource HeaderBackgroundBrush}" BorderBrush="{StaticResource HeaderBackgroundBorderBrush}" BorderThickness="1,0,1,1"/>
<TextBlock Grid.Column="1" VerticalAlignment="Center">Profile:</TextBlock>
<ComboBox Grid.Column="2" Margin="8,4" ItemsSource="{Binding Source={StaticResource ProfilesViewSource}}" SelectedItem="{Binding selected_profile}" ItemTemplateSelector="{StaticResource ProfileItemTemplateSelector}">
<ComboBox.GroupStyle>
@@ -547,52 +547,25 @@
</MenuItem.ToolTip>
</MenuItem>
<Separator VerticalAlignment="Stretch" Width="1" Margin="2,2,1,2" Background="Gray"/>
<c:ActionMenu Grid.Column="4" HorizontalAlignment="Right" Margin="0,0,4,0" VerticalAlignment="Center" ItemsSource="{Binding action_menu_items}"/>
<c:ActionMenu Grid.Column="4" HorizontalAlignment="Right" Margin="0,0,4,0" ItemsSource="{Binding action_menu_items}" Style="{StaticResource SparkToolBarItem}"/>
</ToolBar>
<usp:LadderSettingsView Grid.Row="1" Grid.Column="1" Grid.ColumnSpan="3" HorizontalAlignment="Left" Width="758" MinHeight="234" Margin="0,10,8,0" DataContext="{Binding selected_profile.value}"/>
</Grid>
</DataTemplate>
</u:TemplateSelectorKeyValue>
<u:TemplateSelectorKeyValue Key="{x:Static usp:GridSettingsTemplateKeys.OrderBook}">
<DataTemplate>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<c:ActionMenu HorizontalAlignment="Right" Margin="0,2,0,0" DataContext="{Binding DataContext, RelativeSource={RelativeSource AncestorType=usp:ShellSettingsDialog}}" ItemsSource="{Binding action_menu_items}"/>
<usp:GridSettingsView
Grid.Row="1"
DataContext="{Binding core_settings_vm}"
GridSectionContent="{Binding DataContext.instance_vm, RelativeSource={RelativeSource AncestorType=usp:ShellSettingsDialog}}"
CellContentTemplate="{DynamicResource DefaultCellContentTemplate}"
>
<usp:GridSettingsView.GridSectionContentTemplate>
<DataTemplate>
<StackPanel Grid.IsSharedSizeScope="True">
<ContentControl Content="{Binding core_settings_vm}" ContentTemplate="{StaticResource GridSectionCoreContentTemplate}"/>
<CheckBox Content="Show Full Algo Tree">
<CheckBox.Margin>
<Thickness Top="{StaticResource VerticalPush1}"/>
</CheckBox.Margin>
<CheckBox.IsChecked>
<Binding Path="(orderbook:OrderBookSettingsVM.algo_tree_display_mode)">
<Binding.Converter>
<cv:ReverseConverter>
<cv:BooleanToObjectConverter ValueForFalse="{x:Static orderbook:AlgoTreeDisplayMode.RootAndLeafNodes}" ValueForTrue="{x:Static orderbook:AlgoTreeDisplayMode.FullTree}"/>
</cv:ReverseConverter>
</Binding.Converter>
</Binding>
</CheckBox.IsChecked>
</CheckBox>
</StackPanel>
</DataTemplate>
</usp:GridSettingsView.GridSectionContentTemplate>
</usp:GridSettingsView>
</Grid>
</DataTemplate>
<DataTemplate DataType="{x:Type orderbook:OrderBookSettingsVM}">
<ScrollViewer HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Auto">
<usp:OrderBookSettingsView/>
</ScrollViewer>
</DataTemplate>
</u:TemplateSelectorKeyValue>
<u:TemplateSelectorKeyValue Key="{x:Static usp:GridSettingsTemplateKeys.PNL}">
<DataTemplate>
<usp:PNLSettingsView/>
</DataTemplate>
</u:TemplateSelectorKeyValue>
<u:TemplateSelectorKeyValue Key="BahBahBlackSheep">
<DataTemplate>
<Grid>
<Grid.RowDefinitions>
@@ -770,17 +743,16 @@
LeftItems="{Binding left_skirt_items}"
LeftItemContainerStyleSelector="{StaticResource LeftButtonStyleSelector}"
RightItems="{Binding right_skirt_items}"
BorderBrush="DarkGray"
BorderThickness="0,1,0,0"
BorderThickness="0"
>
<c:DialogSkirt.Background>
<!--<c:DialogSkirt.Background>
<LinearGradientBrush StartPoint="0,0" EndPoint="1,0">
<LinearGradientBrush.GradientStops>
<GradientStop Color="Transparent"/>
<GradientStop Color="{x:Static u:BTSColors.dialog_skirt}" Offset="1"/>
</LinearGradientBrush.GradientStops>
</LinearGradientBrush>
</c:DialogSkirt.Background>
</c:DialogSkirt.Background>-->
</c:DialogSkirt>
</Grid>
</Window>
@@ -125,6 +125,7 @@ namespace mbts.ui.spark {
static class GridSettingsTemplateKeys {
public const string
SimpleGrid = nameof(SimpleGrid),
Fills = nameof(Fills),
Ladder = nameof(Ladder),
OrderBook = nameof(OrderBook),
PNL = nameof(PNL);
@@ -33,9 +33,7 @@ namespace mbts.spark {
public ShellSettingsDialogVM(IWindowSettingsManager<T> settings_manager, Func<UserSettings, T> get_factory_settings, string settings_file_ext, Func<T, TVM> vm_factory, Func<T, T> merge_with_current = null, bool show_save_as_defaults = true) {
if (merge_with_current == null)
merge_with_current = s => s;
// by default, each new window will use their own instance settings
_use_default = backing(nameof(use_default), settings_manager.instance_settings == null);
default_vm = vm_factory(settings_manager.default_settings);
// instance vm is what will always be visible on the screen
instance_vm = vm_factory(settings_manager.current_settings());
// items
@@ -70,8 +68,6 @@ namespace mbts.spark {
// maybe dispose vms
if (instance_vm is IDisposable dispose_ivm)
_resources.Add(dispose_ivm);
if (default_vm is IDisposable dispose_dvm)
_resources.Add(dispose_dvm);
// local methods
void apply() {
var result = merge_with_current(instance_vm.get_result());
@@ -99,7 +95,6 @@ namespace mbts.spark {
public string title { get => _title; set => Set(ref _title, value); }
public TVM instance_vm { get; }
public TVM default_vm { get; }
public IReadOnlyCollection<IMenuItemVM> action_menu_items { get; }
public IEnumerable<DialogSkirtItem> left_skirt_items => _left_skirt_items.Value;
public IEnumerable<DialogSkirtItem> right_skirt_items {
@@ -438,4 +433,4 @@ namespace mbts.spark {
}
}
}
}
}
@@ -24,6 +24,9 @@ namespace mbts.ui.utilities {
SnakeToTitleCaseConverter = ValueConverter.create(obj => snake_to_title_case(obj?.ToString())),
ExpressionSnakeToTitleCaseConverter = ValueConverter.create(obj => snake_to_title_case(obj?.ToString(), @"[A-Za-z0-9_\-\s\(\)<>=]*"));
public static string expression_snake_to_title_case(string expression, TextCapitalizationFormat capitalization_format = TextCapitalizationFormat.Default) =>
snake_to_title_case(expression, @"[A-Za-z0-9_\-\s\(\)<>=]*");
// Adds spaces to CamelCase => "Camel Case"
public static string camel_to_title_case(string name, TextCapitalizationFormat capitalization_format = TextCapitalizationFormat.Default) {
if (name != null) {
+16 -6
View File
@@ -31,9 +31,9 @@ namespace mbts.ui.utility.text {
create(font_family, TextUtils.from_open_type_weight_safe(font_weight));
public static GlyphContext create(string font_family, FontWeight font_weight) =>
create(new System.Windows.Media.FontFamily(font_family), font_weight);
create(new FontFamily(font_family), font_weight);
public static GlyphContext create(System.Windows.Media.FontFamily font_family, FontWeight font_weight) {
public static GlyphContext create(FontFamily font_family, FontWeight font_weight) {
var typeface = new Typeface(font_family, FontStyles.Normal, font_weight, FontStretches.Normal);
return new GlyphContext(typeface.TryGetGlyphTypeface(out GlyphTypeface gt) ? gt : _default_glyph_typeface);
}
@@ -45,10 +45,10 @@ namespace mbts.ui.utility.text {
string family = _glyph_typeface.FamilyNames.FirstOrDefault().Value;
if (string.IsNullOrEmpty(family)) // idk how this would happen, but just to be safe
family = TextUtils.StandardFontFamilyName;
return create(new System.Windows.Media.FontFamily(family), font_weight);
return create(new FontFamily(family), font_weight);
}
public GlyphContext with(System.Windows.Media.FontFamily font_family) =>
public GlyphContext with(FontFamily font_family) =>
create(font_family, _glyph_typeface.Weight);
public GlyphRun get_glyph_run(
@@ -72,8 +72,16 @@ namespace mbts.ui.utility.text {
get_glyph_run(text, font_size, 0, 0, available_width, available_height, x_alignment, y_alignment, padding);
public GlyphRun get_glyph_run(
string text, double font_size, double left, double top, double right, double bottom,
HorizontalAlignment x_alignment, VerticalAlignment y_alignment, Thickness padding) {
string text,
double font_size,
double left,
double top,
double right,
double bottom,
HorizontalAlignment x_alignment,
VerticalAlignment y_alignment,
Thickness padding,
bool use_layout_rounding = true) {
//
if (string.IsNullOrEmpty(text))
return null;
@@ -82,6 +90,8 @@ namespace mbts.ui.utility.text {
if (!_try_get_glyph_params(text, font_size, ref glyph_indices, ref advance_widths, out double glyph_width))
return null;
var origin = _calculate_glyph_run_origin(font_size, left, top, right, bottom, glyph_width, x_alignment, y_alignment, padding);
if (use_layout_rounding)
origin = origin.RoundToLayout();
return new GlyphRun(_glyph_typeface, 0, false, font_size, HighDpiExtensions.fdpi_ratio, glyph_indices, origin, advance_widths, null, null, null, null, null, null);
}
@@ -0,0 +1,67 @@
using System.Collections;
using System.Windows;
using System.Windows.Controls;
namespace mbts.ui {
// This class is a FrameworkElement so it can be generated by a DataTemplate.
// Such a DataTemplate may be referenced by multiple grids,
// which is useful because single instances of row/column definitions cannot be shared.
class GridArchitectureProxy : FrameworkElement {
public static readonly DependencyProperty TemplateProperty =
DependencyProperty.RegisterAttached("Template", typeof(DataTemplate), typeof(GridArchitectureProxy), new PropertyMetadata(OnTemplateChanged));
static void OnTemplateChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) {
if (e.NewValue is DataTemplate dt) {
var target = (Grid)d;
var content = dt.LoadContent();
if (content is GridArchitectureProxy gtp) {
var row_defs = gtp.RowDefinitions;
if (row_defs != null) {
target.RowDefinitions.Clear();
target.RowDefinitions.AddRange(row_defs);
}
var col_defs = gtp.ColumnDefinitions;
if (col_defs != null) {
target.ColumnDefinitions.Clear();
target.ColumnDefinitions.AddRange(col_defs);
}
}
} else if (e.NewValue is GridArchitectureProxy gp) {
var target = (Grid)d;
// Note that this does not react to the rows/columns changing,
// the expectation was that these would be assigned once in practice,
// so implementing such 'row/columndefs changed' events/callbacks appeared unneccesary.
// Anyone can implement it later, if the need arises.
var row_defs = gp.RowDefinitions;
if (row_defs != null) {
target.RowDefinitions.Clear();
target.RowDefinitions.AddRange(row_defs);
}
var col_defs = gp.ColumnDefinitions;
if (col_defs != null) {
target.ColumnDefinitions.Clear();
target.ColumnDefinitions.AddRange(col_defs);
}
}
}
public static DataTemplate GetTemplate(DependencyObject d) => d.GetValue(TemplateProperty) as DataTemplate;
public static void SetTemplate(DependencyObject d, object value) => d.SetValue(TemplateProperty, value);
public static readonly DependencyProperty RowDefinitionsProperty =
WPFHelper.create_dp<IEnumerable, GridArchitectureProxy>(nameof(RowDefinitions));
public IEnumerable RowDefinitions {
get => GetValue(RowDefinitionsProperty) as IEnumerable;
set => SetValue(RowDefinitionsProperty, value);
}
public static readonly DependencyProperty ColumnDefinitionsProperty =
WPFHelper.create_dp<IEnumerable, GridArchitectureProxy>(nameof(ColumnDefinitions));
public IEnumerable ColumnDefinitions {
get => GetValue(ColumnDefinitionsProperty) as IEnumerable;
set => SetValue(ColumnDefinitionsProperty, value);
}
}
}
+234
View File
@@ -0,0 +1,234 @@
using mbts.ui.shell;
using mbts.utils;
using System;
using System.Collections.Generic;
namespace bts.utils {
readonly struct Named<T> : IEquatable<Named<T>>, IComparable<Named<T>>, IComparable {
public readonly string name;
public readonly T value;
public Named(string name, T value) {
this.name = name;
this.value = value;
}
public int CompareTo(Named<T> other) {
int comp = string.Compare(name, other.name);
if (comp == 0)
comp = Comparer<T>.Default.Compare(value, other.value);
return comp;
}
int IComparable.CompareTo(object obj) =>
obj is Named<T> other ? CompareTo(other) : throw ExceptionBuilder.Argument.not_of_type<Named<T>>(obj, nameof(obj));
public bool Equals(Named<T> other) =>
name == other.name &&
Equals(value, other.value);
public override bool Equals(object obj) =>
obj is Named<T> other && Equals(other);
public override int GetHashCode() =>
EqualityComparer.auto_phase(
name?.GetHashCode() ?? 0,
value?.GetHashCode() ?? 0);
public override string ToString() =>
name;
}
class NamedVM : ViewModelBase, IEquatable<NamedVM> {
string _name;
public NamedVM() { }
public NamedVM(string name) =>
this.name = name;
public NamedVM(string name, bool is_read_only) {
this.name = name;
this.is_read_only = is_read_only;
}
public bool is_read_only { get; }
public string name {
get => _name;
set {
_assert_can_write();
Set(ref _name, value);
}
}
public bool Equals(NamedVM other) =>
other != null &&
name == other.name;
public override bool Equals(object obj) =>
is_read_only ?
Equals(obj as NamedVM) :
base.Equals(obj);
public override int GetHashCode() =>
is_read_only ?
_name == null ? 0 : _name.GetHashCode() :
base.GetHashCode();
void _assert_can_write() {
if (is_read_only)
throw new InvalidOperationException("NamedVM is read only.");
}
}
class NamedVM<T> : ViewModelBase, IEquatable<NamedVM<T>> {
string _name;
T _value;
public NamedVM() { }
public NamedVM(string name) =>
_name = name;
public NamedVM(string name, T value)
: this(name) => _value = value;
public NamedVM(string name, T value, bool is_name_read_only, bool is_value_read_only)
: this(name, value) {
this.is_name_read_only = is_name_read_only;
this.is_value_read_only = is_value_read_only;
}
public bool is_name_read_only { get; }
public bool is_value_read_only { get; }
public string name {
get => _name;
set {
if (is_name_read_only)
throw ExceptionBuilder.InvalidOperation.is_read_only();
Set(ref _name, value);
}
}
public T value {
get => _value;
set {
if (is_value_read_only)
throw ExceptionBuilder.InvalidOperation.is_read_only();
Set(ref _value, value);
}
}
public bool Equals(NamedVM<T> other) =>
other != null &&
name == other.name &&
EqualityComparer<T>.Default.Equals(value, other.value);
public override bool Equals(object obj) {
if (obj == this)
return true;
/// 'this' and 'obj' must be entirely non-mutable (<see cref="is_name_read_only"/>, <see cref="is_value_read_only"/>)
/// to be considered equivalent, in addition to having equal <see cref="value"/> and <see cref="name"/> members.
if (!(obj is NamedVM<T> other))
return false;
return is_value_read_only && is_name_read_only &&
other.is_name_read_only && other.is_value_read_only &&
name == other.name && EqualityComparer<T>.Default.Equals(value, other.value);
}
public override int GetHashCode() {
// There are 2 main components to hash: name and value.
// Each shall use 16 bits of the 32 available to create the hash code.
// Both components also have a corresponding 'is_read_only' property,
// which shall be represented as a flag in the first bit of the 16 appropriated to each component.
// If a component is not read only, it must not be included in the hash code (technically, it will be represented as a constant: 0).
int hash = 0;
if (is_name_read_only) {
hash |= 1;
if (name != null)
hash |= ((name.GetHashCode() % (1 << 14)) << 1);
}
if (is_value_read_only) {
hash |= (1 << 16);
if (value != null)
hash |= ((value.GetHashCode() % (1 << 14)) << 17);
}
return hash;
}
}
readonly struct Overridable<T> : IEquatable<Overridable<T>> {
public readonly T value, default_value;
public readonly bool is_overridden, can_override;
public Overridable(T value, T default_value, bool can_override, bool is_overridden) {
this.value = value;
this.default_value = default_value;
this.can_override = can_override;
this.is_overridden = is_overridden;
}
public T effective_value => is_overridden ? value : default_value;
public bool effective_overridden => can_override && is_overridden;
public Overridable<T> with(bool? can_override = null, bool? is_overridden = null) =>
new Overridable<T>(
value,
default_value,
can_override ?? this.can_override,
is_overridden ?? this.is_overridden);
public Overridable<T> with_default(T default_value) =>
new Overridable<T>(
value,
default_value,
can_override,
is_overridden);
public Overridable<T> with_override(T override_value) =>
new Overridable<T>(
override_value,
default_value,
can_override,
is_overridden);
public bool Equals(Overridable<T> other) =>
is_overridden == other.is_overridden &&
can_override == other.can_override &&
Equals(value, other.value) &&
Equals(default_value, other.default_value);
public override bool Equals(object obj) =>
obj is Overridable<T> other && Equals(other);
public override int GetHashCode() {
int hash = (value.GetHashCode() % (1 << 16)) |
((default_value.GetHashCode() % (1 << 16)) << 16);
if (is_overridden)
hash |= 1 << 31;
return hash;
}
}
class OverridableComparer<T> : IEqualityComparer<Overridable<T>> {
readonly IEqualityComparer<T> _value_comparer;
public OverridableComparer(IEqualityComparer<T> value_comparer) =>
_value_comparer = value_comparer;
public bool Equals(Overridable<T> x, Overridable<T> y) =>
x.is_overridden == y.is_overridden &&
x.can_override == y.can_override &&
_value_comparer.Equals(x.value, y.value) &&
_value_comparer.Equals(x.default_value, y.default_value);
public int GetHashCode(Overridable<T> obj) {
int hash = (_value_comparer.GetHashCode(obj.value) % (1 << 16)) |
((_value_comparer.GetHashCode(obj.default_value) % (1 << 16)) << 16);
if (obj.is_overridden)
hash |= 1 << 31;
return hash;
}
}
}
+76 -13
View File
@@ -4,6 +4,7 @@ using mbts.ui.extensions;
using mbts.ui.shell;
using mbts.ui.utility;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.Reactive.Disposables;
@@ -44,11 +45,20 @@ namespace mbts.ui {
StateProperty.OverrideMetadata(typeof(MouseStateDefinition), StateProperty.DefaultMetadata.with(coerce_state));
object coerce_state(DependencyObject d, object baseValue) {
var msd = (MouseStateDefinition)d;
var src = msd.MouseSource;
return new StringBuilderEx(
msd.Mode?.ToString() ?? string.Empty,
msd.MouseSource?.IsMouseOver ?? false ?
(Mouse.LeftButton == MouseButtonState.Pressed ? msd.PressedState : msd.HoverState) :
msd.NormalState).ToString();
src != null && src.IsMouseOver ?
(OneOf.infer<bool, ButtonBase, MenuItem, FrameworkElement>(src, is_button_pressed, is_menu_item_pressed, is_element_pressed) ?
msd.PressedState :
msd.HoverState) :
msd.NormalState).ToString();
// is pressed impls
bool is_button_pressed(ButtonBase bb) => bb.IsPressed;
bool is_menu_item_pressed(MenuItem mi) => mi.IsPressed;
bool is_element_pressed(FrameworkElement el) =>
(Mouse.LeftButton == MouseButtonState.Pressed || Mouse.RightButton == MouseButtonState.Pressed) &&
(el.IsMouseOver || el.IsMouseCaptured);
}
}
@@ -85,16 +95,22 @@ namespace mbts.ui {
Disposable.dispose(ref _mouse_hook);
this.set_current_if_changed(StateProperty, NormalState);
} else {
Disposable.replace(ref _mouse_hook,
Disposable.Create(
mouse_source.SubscribeMouseEnter(on_mouse_event),
mouse_source.SubscribeMouseMove(on_mouse_event),
mouse_source.SubscribeMouseLeave(on_mouse_event),
mouse_source.SubscribePreviewMouseDown(on_mouse_button_event),
mouse_source.SubscribePreviewMouseUp(on_mouse_button_event)));
Disposable.dispose(ref _mouse_hook);
_mouse_hook = Disposable.Create(
mouse_source.SubscribeMouseEnter(on_mouse_event),
mouse_source.SubscribeMouseMove(on_mouse_event),
mouse_source.SubscribeMouseLeave(on_mouse_event),
mouse_source.SubscribePreviewMouseDown(on_mouse_event),
mouse_source.SubscribePreviewMouseUp(on_mouse_event));
if (mouse_source is ButtonBase bb) {
bb.Click += on_mouse_event;
_mouse_hook = _mouse_hook.Append(() => bb.Click -= on_mouse_event);
} else if (mouse_source is MenuItem mi) {
mi.Click += on_mouse_event;
_mouse_hook = _mouse_hook.Append(() => mi.Click -= on_mouse_event);
}
CoerceValue(StateProperty);
void on_mouse_event(object sender, MouseEventArgs e) => CoerceValue(StateProperty);
void on_mouse_button_event(object sender, MouseButtonEventArgs e) => CoerceValue(StateProperty);
void on_mouse_event(object sender, RoutedEventArgs e) => CoerceValue(StateProperty);
}
}
}
@@ -217,7 +233,7 @@ namespace mbts.ui {
}
[ContentProperty(nameof(StateDefinitions))]
class StateGroupsManager : DependencyObject {
class StateGroupsManager : FrameworkElement {
IDisposable _state_definitions_hook;
public bool OverrideTarget { get; set; }
@@ -290,6 +306,36 @@ namespace mbts.ui {
// </VisualStateGroup>
//</VisualStateManager.VisualStateGroups>
class StateManager : DependencyObject {
// This is workaround to name VisualStates/Groups in a ResourceDictionary.xaml file.
public static readonly DependencyProperty NameProperty =
DependencyProperty.RegisterAttached("Name", typeof(string), typeof(StateManager),
new PropertyMetadata(OnNameChanged));
static void OnNameChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) {
if (d is VisualState state)
state.Name = e.NewValue?.ToString();
else if (d is VisualStateGroup group)
group.Name = e.NewValue?.ToString();
}
public static string GetName(DependencyObject d) => d.GetValue(NameProperty) as string;
public static void SetName(DependencyObject d, string value) => d.SetValue(NameProperty, value);
public static readonly DependencyProperty VisualStateGroupsProxyProperty =
DependencyProperty.RegisterAttached("VisualStateGroupsProxy", typeof(IEnumerable<VisualStateGroup>), typeof(StateManager), new PropertyMetadata(OnVisualStateGroupsProxyChanged));
static void OnVisualStateGroupsProxyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) {
var el = (FrameworkElement)d;
var groups = VisualStateManager.GetVisualStateGroups(el);
groups.Clear();
if (e.NewValue is IEnumerable<VisualStateGroup> new_state_groups) {
groups.AddRange(new_state_groups);
}
}
public static IEnumerable<VisualStateGroup> GetVisualStateGroupsProxy(DependencyObject d) => d.GetValue(VisualStateGroupsProxyProperty) as IEnumerable<VisualStateGroup>;
public static void SetVisualStateGroupsProxy(DependencyObject d, IEnumerable<VisualStateGroup> value) => d.SetValue(VisualStateGroupsProxyProperty, value);
#region Groups
public static readonly DependencyProperty GroupsProperty =
DependencyProperty.RegisterAttached("Groups", typeof(StateGroupsManager), typeof(StateManager), new PropertyMetadata((d, e) => {
@@ -301,6 +347,23 @@ namespace mbts.ui {
public static void SetGroups(DependencyObject d, StateGroupsManager value) => d.SetValue(GroupsProperty, value);
#endregion
#region GroupsTemplate
public static readonly DependencyProperty GroupsTemplateProperty =
DependencyProperty.RegisterAttached("GroupsTemplate", typeof(DataTemplate), typeof(StateManager), new PropertyMetadata(OnGroupsTemplateChanged));
static void OnGroupsTemplateChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) {
if (e.NewValue is DataTemplate dt) {
var target = (FrameworkElement)d;
var content = dt.LoadContent();
if (content is StateGroupsManager sgm)
target.SetCurrentValue(GroupsProperty, sgm);
}
}
public static DataTemplate GetGroupsTemplate(DependencyObject d) => d.GetValue(GroupsTemplateProperty) as DataTemplate;
public static void SetGroupsTemplate(DependencyObject d, object value) => d.SetValue(GroupsTemplateProperty, value);
#endregion
public static string GetVisualState(DependencyObject obj) {
return (string)obj.GetValue(VisualStateProperty);
}
+29 -27
View File
@@ -1,5 +1,4 @@
using bts.utils;
using mbts.spark.utilities;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
@@ -10,25 +9,22 @@ using System.Windows.Documents;
using System.Windows.Media;
namespace mbts.ui.utility.text {
public enum FontFamily {
Standard,
Unknown
}
static class TextUtils {
public const string WPFNewLine = "&#x0a;";
public static readonly string StandardFontFamilyName;
public static readonly System.Windows.Media.FontFamily StandardFontFamily;
public static readonly FontFamily StandardFontFamily;
public static readonly Typeface StandardTypeface;
public static readonly GlyphTypeface StandardGlyphTypeface;
public static readonly GlyphContext StandardGlyphContext;
public static readonly IReadOnlyList<FontFamily> SystemFontFamilies;
public static readonly IReadOnlyList<FontWeight> SystemFontWeights;
static TextUtils() {
// try to guarentee a standard font
var preferred_fonts = new[] {
new System.Windows.Media.FontFamily("Segoe UI"),
new FontFamily("Segoe UI"),
SystemFonts.MessageFontFamily,
(System.Windows.Media.FontFamily)TextElement.FontFamilyProperty.DefaultMetadata.DefaultValue
(FontFamily)TextElement.FontFamilyProperty.DefaultMetadata.DefaultValue
};
foreach (var f in preferred_fonts) {
StandardTypeface = new Typeface(f, FontStyles.Normal, FontWeights.Normal, FontStretches.Normal);
@@ -39,6 +35,18 @@ namespace mbts.ui.utility.text {
break;
}
}
var families = Fonts.SystemFontFamilies.ToList();
families.Sort(CompareUtils.safe_comparison<FontFamily>(
(a, b) => a.FamilyNames.First().Value.CompareTo(b.FamilyNames.First().Value)));
SystemFontFamilies = families;
var weights = typeof(FontWeights).GetProperties(BindingFlags.Public | BindingFlags.Static)
.Where(p => p.PropertyType == typeof(FontWeight))
.Select(p => (FontWeight)p.GetValue(null))
.Distinct()
.ToList();
weights.Sort((x, y) => x.ToOpenTypeWeight().CompareTo(y.ToOpenTypeWeight()));
SystemFontWeights = weights;
}
public static FormattedText GetFormattedText(string text) { return GetFormattedText(text, FontSizes.Regular); }
@@ -64,7 +72,7 @@ namespace mbts.ui.utility.text {
public static Typeface with(this Typeface typeface, FontWeight font_weight) =>
new Typeface(typeface.FontFamily, typeface.Style, font_weight, typeface.Stretch);
public static Typeface with(this Typeface typeface, System.Windows.Media.FontFamily font_family) =>
public static Typeface with(this Typeface typeface, FontFamily font_family) =>
new Typeface(font_family, typeface.Style, typeface.Weight, typeface.Stretch);
/// <summary>
@@ -100,32 +108,26 @@ namespace mbts.ui.utility.text {
}
}
public static FontFamily ParseFontFamily(string family_name) {
// TODO
switch (family_name) {
default: return FontFamily.Standard;
}
}
public static IEnumerable<FontWeight> available_weights(this System.Windows.Media.FontFamily family) =>
public static IEnumerable<FontWeight> available_weights(this FontFamily family) =>
family == null ? new FontWeight[] {
FontWeights.Light,
FontWeights.Normal,
FontWeights.SemiBold,
FontWeights.Bold,
FontWeights.Heavy
}.AsEnumerable() :
family.FamilyTypefaces
.Select(ft => ft.Weight)
.Distinct()
.OrderBy(fw => fw.ToOpenTypeWeight());
public static IEnumerable<FontWeight> all_font_weights() =>
typeof(FontWeights).GetProperties(BindingFlags.Public | BindingFlags.Static)
.Where(p => p.PropertyType == typeof(FontWeight))
.Select(p => (FontWeight)p.GetValue(null)).Distinct();
public static string name(this System.Windows.Media.FontFamily font_family) =>
public static string name(this FontFamily font_family) =>
font_family.FamilyNames.Values.FirstOrDefault() ?? string.Empty;
public static string display_name(this System.Windows.Media.FontFamily family, double size) =>
public static string display_name(this FontFamily family, double size) =>
$"{family}, {size} pt";
public static string display_name(this System.Windows.Media.FontFamily family, FontWeight weight, double size) =>
public static string display_name(this FontFamily family, FontWeight weight, double size) =>
$"{family} {weight}, {size} pt";
public static bool is_space_type(this char c) => CharSets.spaces.Contains(c);
+2 -2
View File
@@ -376,7 +376,7 @@ namespace mbts.ui {
switch (x_content_align) {
case HorizontalAlignment.Left: content_left = border_thickness.Left + padding.Left; break;
case HorizontalAlignment.Center: content_left = 0.5 * (result_width - content_width); break;
case HorizontalAlignment.Right: content_left = result_width - border_thickness.Right - padding.Right; break;
case HorizontalAlignment.Right: content_left = result_width - border_thickness.Right - padding.Right - content_width; break;
default: goto case HorizontalAlignment.Center;
}
} else {
@@ -408,7 +408,7 @@ namespace mbts.ui {
switch (y_content_align) {
case VerticalAlignment.Top: content_top = border_thickness.Top + padding.Top; break;
case VerticalAlignment.Center: content_top = 0.5 * (result_height - content_height); break;
case VerticalAlignment.Bottom: content_top = result_height - border_thickness.Bottom - padding.Bottom; break;
case VerticalAlignment.Bottom: content_top = result_height - border_thickness.Bottom - padding.Bottom - content_height; break;
default: goto case VerticalAlignment.Center;
}
} else {
@@ -0,0 +1,11 @@
namespace mbts.ui.viewmodels {
class BooleanParameterVM : ParameterVM<bool> {
public BooleanParameterVM(string name)
: base(name) { }
}
class BooleanOverrideParameterVM : ParameterOverrideVM<bool> {
public BooleanOverrideParameterVM(string name)
: base(name) { }
}
}
@@ -0,0 +1,33 @@
using mbts.ui.controls.color_picker;
using System.Collections.ObjectModel;
using System.Linq;
using System.Windows.Media;
namespace mbts.ui.viewmodels {
class ColorParameterVM : ParameterVM<Color> {
ObservableCollection<ColorPaletteVM> _palettes;
public ColorParameterVM(string name)
: base(name) =>
value = Colors.Black;
public ObservableCollection<ColorPaletteVM> palettes => _palettes ??
(_palettes = new ObservableCollection<ColorPaletteVM>(
ColorPalette.all_presets()
.Select(p => new ColorPaletteVM(p))));
}
class ColorOverrideParameterVM : ParameterOverrideVM<Color> {
ObservableCollection<ColorPaletteVM> _palettes;
public ColorOverrideParameterVM(string name)
: base(name) { }
public override Color override_value {
get => base.override_value;
set => base.override_value = value;
}
public ObservableCollection<ColorPaletteVM> palettes =>
_palettes ?? (_palettes = new ObservableCollection<ColorPaletteVM>(ColorPalette.all_presets().Select(p => new ColorPaletteVM(p))));
}
}
Loaded 100 of 116 files, more files were not shown because too many files have changed in this diff. Show more