sources/Google.Solutions.Mvvm/Controls/ScreenPicker.cs (135 lines of code) (raw):

// // Copyright 2020 Google LLC // // Licensed to the Apache Software Foundation (ASF) under one // or more contributor license agreements. See the NOTICE file // distributed with this work for additional information // regarding copyright ownership. The ASF licenses this file // to you under the Apache License, Version 2.0 (the // "License"); you may not use this file except in compliance // with the License. You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, // software distributed under the License is distributed on an // "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY // KIND, either express or implied. See the License for the // specific language governing permissions and limitations // under the License. // using Google.Solutions.Common.Diagnostics; using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Drawing; using System.Linq; using System.Windows.Forms; namespace Google.Solutions.Mvvm.Controls { /// <summary> /// Control for picking screens, similar to the one used in /// the 'Display' control panel applet. /// </summary> [SkipCodeCoverage("View")] public partial class ScreenPicker<TModelItem> : UserControl where TModelItem : IScreenPickerModelItem { private Point currentMouseLocation = new Point(0, 0); private ObservableCollection<TModelItem>? model = null; public ScreenPicker() { InitializeComponent(); this.ResizeRedraw = true; this.DoubleBuffered = true; } //--------------------------------------------------------------------- // ScreenIcon. //--------------------------------------------------------------------- internal class ScreenIcon { public TModelItem Model { get; } public Rectangle Bounds { get; } public ScreenIcon( TModelItem screen, Rectangle bounds) { this.Model = screen; this.Bounds = bounds; } } internal IEnumerable<ScreenIcon> Screens { get { if (this.model == null) { return Enumerable.Empty<ScreenIcon>(); } // Calculate a bounding box around all screens. var unionOfAllScreens = new Rectangle(); foreach (var item in this.model) { unionOfAllScreens = Rectangle.Union(unionOfAllScreens, item.ScreenBounds); } var scalingFactor = Math.Min( (double)this.Width / unionOfAllScreens.Width, (double)this.Height / unionOfAllScreens.Height); return this.model .OrderBy(modelItem => modelItem.DeviceName) // Shift bounds so that they have positive coordinates. .Select(modelItem => new ScreenIcon( modelItem, new Rectangle( modelItem.ScreenBounds.X + Math.Abs(unionOfAllScreens.X), modelItem.ScreenBounds.Y + Math.Abs(unionOfAllScreens.Y), modelItem.ScreenBounds.Width, modelItem.ScreenBounds.Height))) // Scale down to size of control. .Select(icon => new ScreenIcon( icon.Model, new Rectangle( (int)((double)icon.Bounds.X * scalingFactor), (int)((double)icon.Bounds.Y * scalingFactor), (int)((double)icon.Bounds.Width * scalingFactor), (int)((double)icon.Bounds.Height * scalingFactor)))) // Add some padding .Select(icon => new ScreenIcon( icon.Model, new Rectangle( icon.Bounds.X + 2, icon.Bounds.Y + 2, icon.Bounds.Width - 4, icon.Bounds.Height - 4))) .ToList(); } } //--------------------------------------------------------------------- // Window events. //--------------------------------------------------------------------- private void ScreenSelector_Paint(object sender, PaintEventArgs e) { using (var pen = new Pen(Color.Black, 2)) { var screenOrdinal = 1; foreach (var screenIcon in this.Screens) { e.Graphics.FillRectangle( screenIcon.Model.IsSelected ? SystemBrushes.Highlight : SystemBrushes.ControlLight, screenIcon.Bounds); e.Graphics.DrawRectangle( pen, screenIcon.Bounds); e.Graphics.DrawString( (screenOrdinal++).ToString(), this.Font, screenIcon.Model.IsSelected ? Brushes.White : SystemBrushes.ControlText, screenIcon.Bounds, new StringFormat { LineAlignment = StringAlignment.Center, Alignment = StringAlignment.Center }); } } } private void ScreenSelector_Click(object sender, EventArgs e) { var selected = this.Screens.FirstOrDefault( s => s.Bounds.Contains(this.currentMouseLocation)); if (selected != null) { // Toggle selected state. selected.Model.IsSelected = !selected.Model.IsSelected; Invalidate(); } } private void ScreenSelector_MouseMove(object sender, MouseEventArgs e) { this.currentMouseLocation = e.Location; } //--------------------------------------------------------------------- // List Binding. //--------------------------------------------------------------------- public void BindCollection(ObservableCollection<TModelItem> model) { this.model = model; } } public interface IScreenPickerModelItem { string DeviceName { get; } Rectangle ScreenBounds { get; } bool IsSelected { get; set; } } }