diff --git a/README.md b/README.md index 178ae1b..9566e37 100644 --- a/README.md +++ b/README.md @@ -3,10 +3,12 @@ [![AppVeyor master][AppVeyorImage]][AppVeyorUrl] [![license][LicenceImage]][LicenceUrl] -A configurable Windows screensaver allowing user specified images to float around the desktop. +A configurable Windows screensaver allowing user specified images to float around the desktop with +composable behaviours and effects. + Multiple images may be specified and the screensaver will select from these images at random. -[Egami Flow Screensaver Screenshot](./resources/EgamiFlowScreensaver.jpg?raw=true) +[Egami Flow Screensaver Screenshot](./resources/EgamiFlowScreensaver.jpg?raw=true) See the [configuration](#configuration) section for more details on the available features. @@ -29,7 +31,32 @@ settings window. The configuration window looks like the image below. On this page you can manage the images that will be randomly selected to float around the screen by the screensaver while it is running, as well as configure a few other settings as follows: +### Contents +- [Images Settings](#images-settings) + * [Add Image](#add-image) + * [Remove Image](#remove-image) + * [Image Emit Rate](#image-emit-rate) + * [Max Image Emit Count](#max-image-emit-count) + * [Image Emit Lifetime](#image-emit-lifetime) + * [Infinite Emit Mode](#infinite-emit-mode) + * [Image Emit Location](#image-emit-location) +- [Manage Emit Behaviours](#manage-emit-behaviours) + * [Configure Behaviour](#configure-behaviour) +- [Background Settings](#background-settings) + * [Desktop](#desktop) + * [Solid Colour](#solid-colour) + * [Image](#image) + + [Image Position](#image-position) + ### Images Settings +#### Add Image +Allows you to select a new image file to add to the list of images that will be randomly selected +when images are created. + +#### Remove Image +Removes the selected image file from the list of images that will be randomly selected when images +are created. + #### Image Emit Rate The number of new images that will be created by the screensaver per second. @@ -37,6 +64,15 @@ The number of new images that will be created by the screensaver per second. The maximum number of images that will be created by the screensaver; when this number of images are floating around the screen, the screensaver will stop creating new images. +#### Image Emit Lifetime +The time that emitted images will stay on the screen for (in milliseconds) if *Infinite Emit Mode* +is enabled. Behaviour transitions may extend this lifetime. + +#### Infinite Emit Mode +Whether or not images will be emitted infinitely. In this mode, images will be destroyed after the +time set by *Image Emit Lifetime*, and may have transitioning effects while they are being destroyed +if any behaviours that allow this are enabled and configured to do so. + #### Image Emit Location The location that images will be emitted at on the screensaver; possible values are described in the following table: @@ -50,16 +86,35 @@ in the following table: | Bottom Right | Images should be emitted from the bottom right of the primary screen. | | Centre | Images should be emitted from the centre of the primary screen. | | Random | Images should be emitted from random locations on the primary screen. | +| Custom... | Images should be emitted from a selected location on the screensaver. This location can be selected with the *Choose Location* button. | + +#### Manage Emit Behaviours + +Displays a window allowing you to manage various effects and behaviours that can be applied to any +images emitted by the screensaver. This window contains a list of available behaviours that you can +enable by ticking the relevant checkbox; a description of the selected behaviour is shown underneath +the list. + +[Egami Flow Apply Behaviours Configuration Screenshot](./resources/EgamiFlowApplyBehaviorsConfiguration.png?raw=true) + +##### Configure Behaviour +Configure the selected behaviour in the behaviours list, if it has any configuration options; the +behaviour must be enabled before you are able to configure it. ### Background Settings #### Desktop The screensaver will take a screenshot of the current desktop and use that as the background. +*(This setting does not currently work on Windows 8 and up; preview mode works fine, however +when the screensaver actually runs, a grey background shows instead.)* -#### Solid Color -The screensaver will display a chosen colour as the background. +#### Solid Colour +The screensaver will display a chosen colour as the background. The colour to be displayed can be +selected by clicking the *Choose Colour* button. #### Image -The screensaver will display a chosen image as the background. +The screensaver will display a chosen image as the background. The image to be displayed can be +selected by clicking the *Choose Image* button. You may also specify the colour to be displayed +behind this image by clicking the *Choose Colour* button. ##### Image Position How the screensaver will position the chosen background image; possible values are described diff --git a/appveyor.yml b/appveyor.yml index 6b76830..5f1a3fd 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -48,7 +48,7 @@ release: 'EgamiFlowScreensaver $(APPVEYOR_REPO_TAG_NAME)' description: '' auth_token: - secure: Btb4eq03u8e4+k4WUyv4mWf0kfmzuxSCRo6u1nfOCtxlr4hrdpB5af5tVpt5q+1q + secure: winth2ckr0q/1fONK0Nz7MixzIikq4sYbi/uKfJ97WYgL6rKwI/TT9PNHXcX2pFF artifact: EgamiFlowScreensaver_Release_Any_CPU draft: false prerelease: false diff --git a/resources/EgamiFlowApplyBehaviorsConfiguration.png b/resources/EgamiFlowApplyBehaviorsConfiguration.png new file mode 100644 index 0000000..fb4fe4a Binary files /dev/null and b/resources/EgamiFlowApplyBehaviorsConfiguration.png differ diff --git a/resources/EgamiFlowScreensaver.jpg b/resources/EgamiFlowScreensaver.jpg index 1cac4a9..a8f4fa2 100644 Binary files a/resources/EgamiFlowScreensaver.jpg and b/resources/EgamiFlowScreensaver.jpg differ diff --git a/resources/EgamiFlowScreensaverConfiguration.png b/resources/EgamiFlowScreensaverConfiguration.png index f072681..22e9e38 100644 Binary files a/resources/EgamiFlowScreensaverConfiguration.png and b/resources/EgamiFlowScreensaverConfiguration.png differ diff --git a/resources/ScreensaverSettings.png b/resources/ScreensaverSettings.png index ba46d9a..889bb70 100644 Binary files a/resources/ScreensaverSettings.png and b/resources/ScreensaverSettings.png differ diff --git a/src/EgamiFlowScreensaver/AlphaChangeScreensaverImageItemBehavior.cs b/src/EgamiFlowScreensaver/AlphaChangeScreensaverImageItemBehavior.cs new file mode 100644 index 0000000..098a4b3 --- /dev/null +++ b/src/EgamiFlowScreensaver/AlphaChangeScreensaverImageItemBehavior.cs @@ -0,0 +1,100 @@ +// +// Copyright (c) Adrian John Dunstan. All rights reserved. +// +// Licensed 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. +// + +namespace Natsnudasoft.EgamiFlowScreensaver +{ + using System; + using Microsoft.Xna.Framework; + + /// + /// Provides a behaviour to apply to an image item that causes the image item to transition from + /// one alpha value to another over a specified time frame. + /// + /// + public sealed class AlphaChangeScreensaverImageItemBehavior : + TransitionScreensaverImageItemBehavior + { + /// + /// Initializes a new instance of the + /// class. + /// + /// The description of the area of the screensaver. + /// The alpha value that the image item this behaviour is applied + /// to will start at. This value can be out of range to delay the start of the transition. + /// + /// The alpha value that the image item this behaviour is applied to + /// will finish at. This value can be out of range to advance to the end of the transition + /// faster. + /// The time the image item this behaviour is applied to will + /// take to transition between the specified alpha values. + /// is + /// . + /// is less + /// than a zero time. + public AlphaChangeScreensaverImageItemBehavior( + ScreensaverArea screensaverArea, + float startAlpha, + float endAlpha, + TimeSpan transitionTime) + : base( + screensaverArea, + transitionTime, + (s, p) => s.Alpha = + MathHelper.Clamp(MathHelper.Lerp(startAlpha, endAlpha, p), 0f, 1f)) + { + } + + /// + /// Initializes a new instance of the + /// class. + /// + /// The description of the area of the screensaver. + /// The alpha value that the image item this behaviour is applied + /// to will start at. This value can be out of range to delay the start of the transition. + /// + /// The alpha value that the image item this behaviour is applied to + /// will finish at. This value can be out of range to advance to the end of the transition + /// faster. + /// The time the image item this behaviour is applied to will + /// take to transition between the specified alpha values. + /// The alpha value that the image item this behaviour is + /// applied to will finish at when it is being destroyed (starting from + /// ). + /// The time the image item this behaviour is applied to + /// will take to transition when it is being destroyed. + /// is + /// . + /// , or + /// is less than a zero time. + public AlphaChangeScreensaverImageItemBehavior( + ScreensaverArea screensaverArea, + float startAlpha, + float endAlpha, + TimeSpan transitionTime, + float endTransitionAlpha, + TimeSpan endTransitionTime) + : base( + screensaverArea, + transitionTime, + (s, p) => s.Alpha = + MathHelper.Clamp(MathHelper.Lerp(startAlpha, endAlpha, p), 0f, 1f), + endTransitionTime, + (s, p) => s.Alpha = + MathHelper.Clamp(MathHelper.Lerp(endAlpha, endTransitionAlpha, p), 0f, 1f)) + { + } + } +} \ No newline at end of file diff --git a/src/EgamiFlowScreensaver/BottomLeftImageEmitDetails.cs b/src/EgamiFlowScreensaver/BottomLeftImageEmitDetails.cs new file mode 100644 index 0000000..6f5c83e --- /dev/null +++ b/src/EgamiFlowScreensaver/BottomLeftImageEmitDetails.cs @@ -0,0 +1,119 @@ +// +// Copyright (c) Adrian John Dunstan. All rights reserved. +// +// Licensed 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. +// + +namespace Natsnudasoft.EgamiFlowScreensaver +{ + using System; + using System.Collections.Generic; + using System.Linq; + using Microsoft.Xna.Framework; + using Microsoft.Xna.Framework.Graphics; + using Natsnudasoft.EgamiFlowScreensaver.Config; + using Natsnudasoft.NatsnudaLibrary; + + /// + /// Provides a class capable of creating locations at the bottom left of a screen for a + /// . + /// + public sealed class BottomLeftImageEmitDetails : ImageEmitDetails + { + /// + /// Initializes a new instance of the class. + /// + /// The description of the area of the screensaver. + /// The + /// representing the configured state of the screensaver. + /// A collection of factories that define how to create + /// behaviours that will be attached to any images emitted by a + /// . + /// , + /// , or + /// is . + public BottomLeftImageEmitDetails( + ScreensaverArea screensaverArea, + ScreensaverConfiguration screensaverConfiguration, + IEnumerable> behaviorFactories) + : base(screensaverArea, screensaverConfiguration, behaviorFactories) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The description of the area of the screensaver. + /// The + /// representing the configured state of the screensaver. + /// A collection of factories that define how to create + /// behaviours that will be attached to any images emitted by a + /// . + /// A pseudo-random number generator that can be used to generate + /// randomness in the . + /// , + /// , , or + /// is . + public BottomLeftImageEmitDetails( + ScreensaverArea screensaverArea, + ScreensaverConfiguration screensaverConfiguration, + IEnumerable> behaviorFactories, + Random random) + : base(screensaverArea, screensaverConfiguration, behaviorFactories, random) + { + } + + /// + /// is + /// . + public override ScreensaverImageItem CreateScreensaverImageItem(Texture2D texture) + { + ParameterValidation.IsNotNull(texture, nameof(texture)); + + var origin = new Vector2(texture.Width, texture.Height) / 2f; + var bottomLeftPrimary = new Point( + this.ScreensaverArea.PrimaryGameBounds.Left, + this.ScreensaverArea.PrimaryGameBounds.Bottom); + var maxX = bottomLeftPrimary.X - texture.Width + origin.X; + var minX = maxX - TwoPositionDistribution; + var minY = bottomLeftPrimary.Y + origin.Y; + var maxY = minY + TwoPositionDistribution; + var position = new Vector2( + this.Random.NextFloat(minX, maxX), + this.Random.NextFloat(minY, maxY)); + return new ScreensaverImageItem( + texture, + this.BehaviorFactories.Select(f => f?.Invoke())) + { + Position = position, + Origin = origin + }; + } + + /// + protected override Func CreateDefaultMovingBehaviorFactory() + { + IScreensaverImageItemBehavior CreateDefaultMovingBehavior() + { + const float minSpeed = MovingScreensaverImageItemBehavior.DefaultMinSpeed; + const float maxSpeed = MovingScreensaverImageItemBehavior.DefaultMaxSpeed; + var speed = new Vector2( + this.Random.NextFloat(minSpeed, maxSpeed), + -this.Random.NextFloat(minSpeed, maxSpeed)); + return new MovingScreensaverImageItemBehavior(this.ScreensaverArea, speed); + } + + return CreateDefaultMovingBehavior; + } + } +} \ No newline at end of file diff --git a/src/EgamiFlowScreensaver/BottomRightImageEmitDetails.cs b/src/EgamiFlowScreensaver/BottomRightImageEmitDetails.cs new file mode 100644 index 0000000..80f23df --- /dev/null +++ b/src/EgamiFlowScreensaver/BottomRightImageEmitDetails.cs @@ -0,0 +1,119 @@ +// +// Copyright (c) Adrian John Dunstan. All rights reserved. +// +// Licensed 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. +// + +namespace Natsnudasoft.EgamiFlowScreensaver +{ + using System; + using System.Collections.Generic; + using System.Linq; + using Microsoft.Xna.Framework; + using Microsoft.Xna.Framework.Graphics; + using Natsnudasoft.EgamiFlowScreensaver.Config; + using Natsnudasoft.NatsnudaLibrary; + + /// + /// Provides a class capable of creating locations at the bottom right of a screen for a + /// . + /// + public sealed class BottomRightImageEmitDetails : ImageEmitDetails + { + /// + /// Initializes a new instance of the class. + /// + /// The description of the area of the screensaver. + /// The + /// representing the configured state of the screensaver. + /// A collection of factories that define how to create + /// behaviours that will be attached to any images emitted by a + /// . + /// , + /// , or + /// is . + public BottomRightImageEmitDetails( + ScreensaverArea screensaverArea, + ScreensaverConfiguration screensaverConfiguration, + IEnumerable> behaviorFactories) + : base(screensaverArea, screensaverConfiguration, behaviorFactories) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The description of the area of the screensaver. + /// The + /// representing the configured state of the screensaver. + /// A collection of factories that define how to create + /// behaviours that will be attached to any images emitted by a + /// . + /// A pseudo-random number generator that can be used to generate + /// randomness in the . + /// , + /// , , or + /// is . + public BottomRightImageEmitDetails( + ScreensaverArea screensaverArea, + ScreensaverConfiguration screensaverConfiguration, + IEnumerable> behaviorFactories, + Random random) + : base(screensaverArea, screensaverConfiguration, behaviorFactories, random) + { + } + + /// + /// is + /// . + public override ScreensaverImageItem CreateScreensaverImageItem(Texture2D texture) + { + ParameterValidation.IsNotNull(texture, nameof(texture)); + + var origin = new Vector2(texture.Width, texture.Height) / 2f; + var bottomRightPrimary = new Point( + this.ScreensaverArea.PrimaryGameBounds.Right, + this.ScreensaverArea.PrimaryGameBounds.Bottom); + var minX = bottomRightPrimary.X + origin.X; + var maxX = minX + TwoPositionDistribution; + var minY = bottomRightPrimary.Y + origin.Y; + var maxY = minY + TwoPositionDistribution; + var position = new Vector2( + this.Random.NextFloat(minX, maxX), + this.Random.NextFloat(minY, maxY)); + return new ScreensaverImageItem( + texture, + this.BehaviorFactories.Select(f => f?.Invoke())) + { + Position = position, + Origin = origin + }; + } + + /// + protected override Func CreateDefaultMovingBehaviorFactory() + { + IScreensaverImageItemBehavior CreateDefaultMovingBehavior() + { + const float minSpeed = MovingScreensaverImageItemBehavior.DefaultMinSpeed; + const float maxSpeed = MovingScreensaverImageItemBehavior.DefaultMaxSpeed; + var speed = new Vector2( + -this.Random.NextFloat(minSpeed, maxSpeed), + -this.Random.NextFloat(minSpeed, maxSpeed)); + return new MovingScreensaverImageItemBehavior(this.ScreensaverArea, speed); + } + + return CreateDefaultMovingBehavior; + } + } +} \ No newline at end of file diff --git a/src/EgamiFlowScreensaver/CenterImageEmitDetails.cs b/src/EgamiFlowScreensaver/CenterImageEmitDetails.cs new file mode 100644 index 0000000..fdf0445 --- /dev/null +++ b/src/EgamiFlowScreensaver/CenterImageEmitDetails.cs @@ -0,0 +1,101 @@ +// +// Copyright (c) Adrian John Dunstan. All rights reserved. +// +// Licensed 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. +// + +namespace Natsnudasoft.EgamiFlowScreensaver +{ + using System; + using System.Collections.Generic; + using System.Linq; + using Microsoft.Xna.Framework; + using Microsoft.Xna.Framework.Graphics; + using Natsnudasoft.EgamiFlowScreensaver.Config; + using Natsnudasoft.NatsnudaLibrary; + + /// + /// Provides a class capable of creating locations at the centre of a screen for a + /// . + /// + public sealed class CenterImageEmitDetails : ImageEmitDetails + { + /// + /// Initializes a new instance of the class. + /// + /// The description of the area of the screensaver. + /// The + /// representing the configured state of the screensaver. + /// A collection of factories that define how to create + /// behaviours that will be attached to any images emitted by a + /// . + /// , + /// , or + /// is . + public CenterImageEmitDetails( + ScreensaverArea screensaverArea, + ScreensaverConfiguration screensaverConfiguration, + IEnumerable> behaviorFactories) + : base(screensaverArea, screensaverConfiguration, behaviorFactories) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The description of the area of the screensaver. + /// The + /// representing the configured state of the screensaver. + /// A collection of factories that define how to create + /// behaviours that will be attached to any images emitted by a + /// . + /// A pseudo-random number generator that can be used to generate + /// randomness in the . + /// , + /// , , or + /// is . + public CenterImageEmitDetails( + ScreensaverArea screensaverArea, + ScreensaverConfiguration screensaverConfiguration, + IEnumerable> behaviorFactories, + Random random) + : base(screensaverArea, screensaverConfiguration, behaviorFactories, random) + { + } + + /// + /// is + /// . + public override ScreensaverImageItem CreateScreensaverImageItem(Texture2D texture) + { + ParameterValidation.IsNotNull(texture, nameof(texture)); + + var origin = new Vector2(texture.Width, texture.Height) / 2f; + var gameCenter = this.ScreensaverArea.PrimaryGameBounds.Center.ToVector2(); + var minX = gameCenter.X - PositionDistribution; + var maxX = gameCenter.X + PositionDistribution; + var minY = gameCenter.Y - PositionDistribution; + var maxY = gameCenter.Y + PositionDistribution; + var position = new Vector2( + this.Random.NextFloat(minX, maxX), + this.Random.NextFloat(minY, maxY)); + return new ScreensaverImageItem( + texture, + this.BehaviorFactories.Select(f => f?.Invoke())) + { + Position = position, + Origin = origin + }; + } + } +} \ No newline at end of file diff --git a/src/EgamiFlowScreensaver/ColorChangeScreensaverImageItemBehavior.cs b/src/EgamiFlowScreensaver/ColorChangeScreensaverImageItemBehavior.cs new file mode 100644 index 0000000..0981768 --- /dev/null +++ b/src/EgamiFlowScreensaver/ColorChangeScreensaverImageItemBehavior.cs @@ -0,0 +1,93 @@ +// +// Copyright (c) Adrian John Dunstan. All rights reserved. +// +// Licensed 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. +// + +namespace Natsnudasoft.EgamiFlowScreensaver +{ + using System; + using Microsoft.Xna.Framework; + + /// + /// Provides a behaviour to apply to an image item that causes the image item to transition from + /// one colour to another over a specified time frame. + /// + /// + public sealed class ColorChangeScreensaverImageItemBehavior : + TransitionScreensaverImageItemBehavior + { + /// + /// Initializes a new instance of the + /// class. + /// + /// The description of the area of the screensaver. + /// The colour that the image item this behaviour is applied to + /// will start at. + /// The colour that the image item this behaviour is applied to will + /// finish at. + /// The time the image item this behaviour is applied to will + /// take to transition between the specified colours. + /// is + /// . + /// is less + /// than a zero time. + public ColorChangeScreensaverImageItemBehavior( + ScreensaverArea screensaverArea, + Color startColor, + Color endColor, + TimeSpan transitionTime) + : base( + screensaverArea, + transitionTime, + (s, p) => s.Color = Color.Lerp(startColor, endColor, p)) + { + } + + /// + /// Initializes a new instance of the + /// class. + /// + /// The description of the area of the screensaver. + /// The colour that the image item this behaviour is applied to + /// will start at. + /// The colour that the image item this behaviour is applied to will + /// finish at. + /// The time the image item this behaviour is applied to will + /// take to transition between the specified colours. + /// The colour value that the image item this behaviour is + /// applied to will finish at when it is being destroyed (starting from + /// ). + /// The time the image item this behaviour is applied to + /// will take to transition when it is being destroyed. + /// is + /// . + /// , or + /// is less than a zero time. + public ColorChangeScreensaverImageItemBehavior( + ScreensaverArea screensaverArea, + Color startColor, + Color endColor, + TimeSpan transitionTime, + Color endTransitionColor, + TimeSpan endTransitionTime) + : base( + screensaverArea, + transitionTime, + (s, p) => s.Color = Color.Lerp(startColor, endColor, p), + endTransitionTime, + (s, p) => s.Color = Color.Lerp(endColor, endTransitionColor, p)) + { + } + } +} \ No newline at end of file diff --git a/src/EgamiFlowScreensaver/Config/AlphaChangeBehaviorForm.Designer.cs b/src/EgamiFlowScreensaver/Config/AlphaChangeBehaviorForm.Designer.cs new file mode 100644 index 0000000..834bb33 --- /dev/null +++ b/src/EgamiFlowScreensaver/Config/AlphaChangeBehaviorForm.Designer.cs @@ -0,0 +1,293 @@ +namespace Natsnudasoft.EgamiFlowScreensaver.Config +{ + partial class AlphaChangeBehaviorForm + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Windows Form Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage( + "Microsoft.Reliability", + "CA2000:Dispose objects before losing scope", + Justification = "This is auto generated code.")] + private void InitializeComponent() + { + this.components = new System.ComponentModel.Container(); + System.Windows.Forms.Panel panel2; + System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(AlphaChangeBehaviorForm)); + System.Windows.Forms.GroupBox configurationGroupBox; + System.Windows.Forms.Label startAlphaLabel; + System.Windows.Forms.Label endAlphaLabel; + System.Windows.Forms.Label transitionTimeLabel; + System.Windows.Forms.Panel panel1; + this.okButton = new System.Windows.Forms.Button(); + this.cancelButton = new System.Windows.Forms.Button(); + this.endTransitionAlphaLabel = new System.Windows.Forms.Label(); + this.endTransitionAlphaNumericUpDownWheel = new Natsnudasoft.EgamiFlowScreensaver.NumericUpDownWheel(); + this.endTransitionTimeLabel = new System.Windows.Forms.Label(); + this.endTransitionTimeNumericUpDownWheel = new Natsnudasoft.EgamiFlowScreensaver.NumericUpDownWheel(); + this.horizontalSplitterLabel = new System.Windows.Forms.Label(); + this.endTransitionEnabledCheckBox = new System.Windows.Forms.CheckBox(); + this.startAlphaNumericUpDownWheel = new Natsnudasoft.EgamiFlowScreensaver.NumericUpDownWheel(); + this.endAlphaNumericUpDownWheel = new Natsnudasoft.EgamiFlowScreensaver.NumericUpDownWheel(); + this.transitionTimeNumericUpDownWheel = new Natsnudasoft.EgamiFlowScreensaver.NumericUpDownWheel(); + this.configurationToolTip = new System.Windows.Forms.ToolTip(this.components); + panel2 = new System.Windows.Forms.Panel(); + configurationGroupBox = new System.Windows.Forms.GroupBox(); + startAlphaLabel = new System.Windows.Forms.Label(); + endAlphaLabel = new System.Windows.Forms.Label(); + transitionTimeLabel = new System.Windows.Forms.Label(); + panel1 = new System.Windows.Forms.Panel(); + panel2.SuspendLayout(); + configurationGroupBox.SuspendLayout(); + ((System.ComponentModel.ISupportInitialize)(this.endTransitionAlphaNumericUpDownWheel)).BeginInit(); + ((System.ComponentModel.ISupportInitialize)(this.endTransitionTimeNumericUpDownWheel)).BeginInit(); + ((System.ComponentModel.ISupportInitialize)(this.startAlphaNumericUpDownWheel)).BeginInit(); + ((System.ComponentModel.ISupportInitialize)(this.endAlphaNumericUpDownWheel)).BeginInit(); + ((System.ComponentModel.ISupportInitialize)(this.transitionTimeNumericUpDownWheel)).BeginInit(); + panel1.SuspendLayout(); + this.SuspendLayout(); + // + // panel2 + // + panel2.Controls.Add(this.okButton); + panel2.Controls.Add(this.cancelButton); + resources.ApplyResources(panel2, "panel2"); + panel2.Name = "panel2"; + // + // okButton + // + resources.ApplyResources(this.okButton, "okButton"); + this.okButton.Name = "okButton"; + this.configurationToolTip.SetToolTip(this.okButton, resources.GetString("okButton.ToolTip")); + this.okButton.UseVisualStyleBackColor = true; + // + // cancelButton + // + resources.ApplyResources(this.cancelButton, "cancelButton"); + this.cancelButton.DialogResult = System.Windows.Forms.DialogResult.Cancel; + this.cancelButton.Name = "cancelButton"; + this.configurationToolTip.SetToolTip(this.cancelButton, resources.GetString("cancelButton.ToolTip")); + this.cancelButton.UseVisualStyleBackColor = true; + // + // configurationGroupBox + // + resources.ApplyResources(configurationGroupBox, "configurationGroupBox"); + configurationGroupBox.Controls.Add(this.endTransitionAlphaLabel); + configurationGroupBox.Controls.Add(this.endTransitionAlphaNumericUpDownWheel); + configurationGroupBox.Controls.Add(this.endTransitionTimeLabel); + configurationGroupBox.Controls.Add(this.endTransitionTimeNumericUpDownWheel); + configurationGroupBox.Controls.Add(this.horizontalSplitterLabel); + configurationGroupBox.Controls.Add(this.endTransitionEnabledCheckBox); + configurationGroupBox.Controls.Add(startAlphaLabel); + configurationGroupBox.Controls.Add(this.startAlphaNumericUpDownWheel); + configurationGroupBox.Controls.Add(endAlphaLabel); + configurationGroupBox.Controls.Add(this.endAlphaNumericUpDownWheel); + configurationGroupBox.Controls.Add(transitionTimeLabel); + configurationGroupBox.Controls.Add(this.transitionTimeNumericUpDownWheel); + configurationGroupBox.Name = "configurationGroupBox"; + configurationGroupBox.TabStop = false; + // + // endTransitionAlphaLabel + // + resources.ApplyResources(this.endTransitionAlphaLabel, "endTransitionAlphaLabel"); + this.endTransitionAlphaLabel.Name = "endTransitionAlphaLabel"; + this.configurationToolTip.SetToolTip(this.endTransitionAlphaLabel, resources.GetString("endTransitionAlphaLabel.ToolTip")); + // + // endTransitionAlphaNumericUpDownWheel + // + resources.ApplyResources(this.endTransitionAlphaNumericUpDownWheel, "endTransitionAlphaNumericUpDownWheel"); + this.endTransitionAlphaNumericUpDownWheel.DecimalPlaces = 5; + this.endTransitionAlphaNumericUpDownWheel.Increment = new decimal(new int[] { + 1, + 0, + 0, + 131072}); + this.endTransitionAlphaNumericUpDownWheel.Maximum = new decimal(new int[] { + 1, + 0, + 0, + 0}); + this.endTransitionAlphaNumericUpDownWheel.Name = "endTransitionAlphaNumericUpDownWheel"; + // + // endTransitionTimeLabel + // + resources.ApplyResources(this.endTransitionTimeLabel, "endTransitionTimeLabel"); + this.endTransitionTimeLabel.Name = "endTransitionTimeLabel"; + this.configurationToolTip.SetToolTip(this.endTransitionTimeLabel, resources.GetString("endTransitionTimeLabel.ToolTip")); + // + // endTransitionTimeNumericUpDownWheel + // + resources.ApplyResources(this.endTransitionTimeNumericUpDownWheel, "endTransitionTimeNumericUpDownWheel"); + this.endTransitionTimeNumericUpDownWheel.DecimalPlaces = 2; + this.endTransitionTimeNumericUpDownWheel.Increment = new decimal(new int[] { + 100, + 0, + 0, + 0}); + this.endTransitionTimeNumericUpDownWheel.Maximum = new decimal(new int[] { + 604800000, + 0, + 0, + 0}); + this.endTransitionTimeNumericUpDownWheel.Name = "endTransitionTimeNumericUpDownWheel"; + // + // horizontalSplitterLabel + // + resources.ApplyResources(this.horizontalSplitterLabel, "horizontalSplitterLabel"); + this.horizontalSplitterLabel.BorderStyle = System.Windows.Forms.BorderStyle.Fixed3D; + this.horizontalSplitterLabel.Name = "horizontalSplitterLabel"; + // + // endTransitionEnabledCheckBox + // + resources.ApplyResources(this.endTransitionEnabledCheckBox, "endTransitionEnabledCheckBox"); + this.endTransitionEnabledCheckBox.Name = "endTransitionEnabledCheckBox"; + this.configurationToolTip.SetToolTip(this.endTransitionEnabledCheckBox, resources.GetString("endTransitionEnabledCheckBox.ToolTip")); + this.endTransitionEnabledCheckBox.UseVisualStyleBackColor = true; + // + // startAlphaLabel + // + resources.ApplyResources(startAlphaLabel, "startAlphaLabel"); + startAlphaLabel.Name = "startAlphaLabel"; + this.configurationToolTip.SetToolTip(startAlphaLabel, resources.GetString("startAlphaLabel.ToolTip")); + // + // startAlphaNumericUpDownWheel + // + resources.ApplyResources(this.startAlphaNumericUpDownWheel, "startAlphaNumericUpDownWheel"); + this.startAlphaNumericUpDownWheel.DecimalPlaces = 5; + this.startAlphaNumericUpDownWheel.Increment = new decimal(new int[] { + 1, + 0, + 0, + 131072}); + this.startAlphaNumericUpDownWheel.Maximum = new decimal(new int[] { + 1, + 0, + 0, + 0}); + this.startAlphaNumericUpDownWheel.Minimum = new decimal(new int[] { + 10, + 0, + 0, + -2147483648}); + this.startAlphaNumericUpDownWheel.Name = "startAlphaNumericUpDownWheel"; + // + // endAlphaLabel + // + resources.ApplyResources(endAlphaLabel, "endAlphaLabel"); + endAlphaLabel.Name = "endAlphaLabel"; + this.configurationToolTip.SetToolTip(endAlphaLabel, resources.GetString("endAlphaLabel.ToolTip")); + // + // endAlphaNumericUpDownWheel + // + resources.ApplyResources(this.endAlphaNumericUpDownWheel, "endAlphaNumericUpDownWheel"); + this.endAlphaNumericUpDownWheel.DecimalPlaces = 5; + this.endAlphaNumericUpDownWheel.Increment = new decimal(new int[] { + 1, + 0, + 0, + 131072}); + this.endAlphaNumericUpDownWheel.Maximum = new decimal(new int[] { + 1, + 0, + 0, + 0}); + this.endAlphaNumericUpDownWheel.Name = "endAlphaNumericUpDownWheel"; + this.endAlphaNumericUpDownWheel.Value = new decimal(new int[] { + 1, + 0, + 0, + 0}); + // + // transitionTimeLabel + // + resources.ApplyResources(transitionTimeLabel, "transitionTimeLabel"); + transitionTimeLabel.Name = "transitionTimeLabel"; + this.configurationToolTip.SetToolTip(transitionTimeLabel, resources.GetString("transitionTimeLabel.ToolTip")); + // + // transitionTimeNumericUpDownWheel + // + resources.ApplyResources(this.transitionTimeNumericUpDownWheel, "transitionTimeNumericUpDownWheel"); + this.transitionTimeNumericUpDownWheel.DecimalPlaces = 2; + this.transitionTimeNumericUpDownWheel.Increment = new decimal(new int[] { + 100, + 0, + 0, + 0}); + this.transitionTimeNumericUpDownWheel.Maximum = new decimal(new int[] { + 604800000, + 0, + 0, + 0}); + this.transitionTimeNumericUpDownWheel.Name = "transitionTimeNumericUpDownWheel"; + // + // panel1 + // + panel1.Controls.Add(configurationGroupBox); + resources.ApplyResources(panel1, "panel1"); + panel1.Name = "panel1"; + // + // AlphaChangeBehaviorForm + // + this.AcceptButton = this.okButton; + resources.ApplyResources(this, "$this"); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.CancelButton = this.cancelButton; + this.Controls.Add(panel1); + this.Controls.Add(panel2); + this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedToolWindow; + this.MaximizeBox = false; + this.MinimizeBox = false; + this.Name = "AlphaChangeBehaviorForm"; + this.ShowIcon = false; + this.ShowInTaskbar = false; + this.SizeGripStyle = System.Windows.Forms.SizeGripStyle.Hide; + panel2.ResumeLayout(false); + configurationGroupBox.ResumeLayout(false); + configurationGroupBox.PerformLayout(); + ((System.ComponentModel.ISupportInitialize)(this.endTransitionAlphaNumericUpDownWheel)).EndInit(); + ((System.ComponentModel.ISupportInitialize)(this.endTransitionTimeNumericUpDownWheel)).EndInit(); + ((System.ComponentModel.ISupportInitialize)(this.startAlphaNumericUpDownWheel)).EndInit(); + ((System.ComponentModel.ISupportInitialize)(this.endAlphaNumericUpDownWheel)).EndInit(); + ((System.ComponentModel.ISupportInitialize)(this.transitionTimeNumericUpDownWheel)).EndInit(); + panel1.ResumeLayout(false); + this.ResumeLayout(false); + + } + + #endregion + + private System.Windows.Forms.Button okButton; + private System.Windows.Forms.ToolTip configurationToolTip; + private System.Windows.Forms.Button cancelButton; + private NumericUpDownWheel transitionTimeNumericUpDownWheel; + private NumericUpDownWheel startAlphaNumericUpDownWheel; + private NumericUpDownWheel endAlphaNumericUpDownWheel; + private System.Windows.Forms.Label endTransitionTimeLabel; + private NumericUpDownWheel endTransitionTimeNumericUpDownWheel; + private System.Windows.Forms.Label horizontalSplitterLabel; + private System.Windows.Forms.CheckBox endTransitionEnabledCheckBox; + private NumericUpDownWheel endTransitionAlphaNumericUpDownWheel; + private System.Windows.Forms.Label endTransitionAlphaLabel; + } +} \ No newline at end of file diff --git a/src/EgamiFlowScreensaver/Config/AlphaChangeBehaviorForm.cs b/src/EgamiFlowScreensaver/Config/AlphaChangeBehaviorForm.cs new file mode 100644 index 0000000..3c3f080 --- /dev/null +++ b/src/EgamiFlowScreensaver/Config/AlphaChangeBehaviorForm.cs @@ -0,0 +1,114 @@ +// +// Copyright (c) Adrian John Dunstan. All rights reserved. +// +// Licensed 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. +// + +namespace Natsnudasoft.EgamiFlowScreensaver.Config +{ + using System; + using System.Windows.Forms; + using Natsnudasoft.NatsnudaLibrary; + + /// + /// A form allowing configuration of a behaviour that causes an image item to transition from + /// one alpha value to another over a specified time frame. + /// + /// + public partial class AlphaChangeBehaviorForm : Form + { + private readonly AlphaChangeBehaviorFormViewModel viewModel; + + /// + /// Initializes a new instance of the class. + /// + /// The view model to bind to. + /// is + /// . + public AlphaChangeBehaviorForm(AlphaChangeBehaviorFormViewModel viewModel) + { + ParameterValidation.IsNotNull(viewModel, nameof(viewModel)); + + this.viewModel = viewModel; + this.InitializeComponent(); + + this.startAlphaNumericUpDownWheel.DataBindings.Add( + nameof(NumericUpDown.Value), + this.viewModel, + nameof(AlphaChangeBehaviorFormViewModel.StartAlpha), + true); + + this.endAlphaNumericUpDownWheel.DataBindings.Add( + nameof(NumericUpDown.Value), + this.viewModel, + nameof(AlphaChangeBehaviorFormViewModel.EndAlpha), + true); + + this.transitionTimeNumericUpDownWheel.DataBindings.Add( + nameof(NumericUpDown.Value), + this.viewModel, + nameof(AlphaChangeBehaviorFormViewModel.TransitionTime), + true); + + this.endTransitionEnabledCheckBox.DataBindings.Add( + nameof(CheckBox.Enabled), + this.viewModel, + nameof(AlphaChangeBehaviorFormViewModel.IsInfiniteImageEmitMode)); + this.endTransitionEnabledCheckBox.DataBindings.Add( + nameof(CheckBox.Checked), + this.viewModel, + nameof(AlphaChangeBehaviorFormViewModel.EndTransitionEnabled), + false, + DataSourceUpdateMode.OnPropertyChanged); + + this.endTransitionAlphaLabel.DataBindings.Add( + nameof(Label.Enabled), + this.viewModel, + nameof(AlphaChangeBehaviorFormViewModel.EndTransitionEnabled)); + + this.endTransitionAlphaNumericUpDownWheel.DataBindings.Add( + nameof(NumericUpDownWheel.Enabled), + this.viewModel, + nameof(AlphaChangeBehaviorFormViewModel.EndTransitionEnabled)); + this.endTransitionAlphaNumericUpDownWheel.DataBindings.Add( + nameof(NumericUpDownWheel.Value), + this.viewModel, + nameof(AlphaChangeBehaviorFormViewModel.EndTransitionAlpha), + true); + + this.endTransitionTimeLabel.DataBindings.Add( + nameof(Label.Enabled), + this.viewModel, + nameof(AlphaChangeBehaviorFormViewModel.EndTransitionEnabled)); + + this.endTransitionTimeNumericUpDownWheel.DataBindings.Add( + nameof(NumericUpDownWheel.Enabled), + this.viewModel, + nameof(AlphaChangeBehaviorFormViewModel.EndTransitionEnabled)); + this.endTransitionTimeNumericUpDownWheel.DataBindings.Add( + nameof(NumericUpDownWheel.Value), + this.viewModel, + nameof(AlphaChangeBehaviorFormViewModel.EndTransitionTime), + true); + + this.okButton.Click += (sender, e) => + { + if (this.viewModel.Validate(this)) + { + this.okButton.Focus(); + this.DialogResult = DialogResult.OK; + } + }; + } + } +} \ No newline at end of file diff --git a/src/EgamiFlowScreensaver/Config/AlphaChangeBehaviorForm.resx b/src/EgamiFlowScreensaver/Config/AlphaChangeBehaviorForm.resx new file mode 100644 index 0000000..9db78a9 --- /dev/null +++ b/src/EgamiFlowScreensaver/Config/AlphaChangeBehaviorForm.resx @@ -0,0 +1,756 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + False + + + okButton + + + System.Windows.Forms.Button, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + panel2 + + + 0 + + + cancelButton + + + System.Windows.Forms.Button, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + panel2 + + + 1 + + + + Right + + + + 241, 0 + + + 0, 0, 0, 0 + + + 8, 8, 8, 8 + + + 128, 252 + + + + 1 + + + panel2 + + + System.Windows.Forms.Panel, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + $this + + + 1 + + + Top, Left, Right + + + NoControl + + + 14, 15 + + + 6, 6, 6, 6 + + + 99, 26 + + + 0 + + + OK + + + 17, 17 + + + Accept the current behaviour configuration changes. + + + okButton + + + System.Windows.Forms.Button, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + panel2 + + + 0 + + + Top, Left, Right + + + NoControl + + + 14, 53 + + + 6, 6, 6, 6 + + + 99, 26 + + + 1 + + + Cancel + + + Close the behaviour configuration without saving changes. + + + cancelButton + + + System.Windows.Forms.Button, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + panel2 + + + 1 + + + False + + + Top, Bottom, Left, Right + + + True + + + NoControl + + + 6, 168 + + + 6, 0, 6, 0 + + + 105, 13 + + + 8 + + + End Transition Alpha + + + MiddleLeft + + + The alpha value that the end transition will finish at (starting from the end alpha value). + + + endTransitionAlphaLabel + + + System.Windows.Forms.Label, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + configurationGroupBox + + + 0 + + + Top, Left, Right + + + 123, 166 + + + 6, 6, 6, 6 + + + 70, 20 + + + 9 + + + endTransitionAlphaNumericUpDownWheel + + + Natsnudasoft.EgamiFlowScreensaver.NumericUpDownWheel, Natsnudasoft.EgamiFlowScreensaver, Version=0.1.0.0, Culture=neutral, PublicKeyToken=null + + + configurationGroupBox + + + 1 + + + True + + + NoControl + + + 6, 200 + + + 6, 0, 6, 0 + + + 101, 13 + + + 10 + + + End Transition Time + + + MiddleLeft + + + The amount of time (in milliseconds) that the end transition will take. This time extends the original image lifetime. + + + endTransitionTimeLabel + + + System.Windows.Forms.Label, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + configurationGroupBox + + + 2 + + + Top, Left, Right + + + 119, 198 + + + 6, 6, 6, 6 + + + 92, 20 + + + 11 + + + endTransitionTimeNumericUpDownWheel + + + Natsnudasoft.EgamiFlowScreensaver.NumericUpDownWheel, Natsnudasoft.EgamiFlowScreensaver, Version=0.1.0.0, Culture=neutral, PublicKeyToken=null + + + configurationGroupBox + + + 3 + + + Top, Left, Right + + + NoControl + + + 0, 123 + + + 0, 6, 0, 6 + + + 217, 2 + + + 6 + + + horizontalSplitterLabel + + + System.Windows.Forms.Label, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + configurationGroupBox + + + 4 + + + Top + + + NoControl + + + 45, 137 + + + 6, 6, 6, 6 + + + 136, 17 + + + 7 + + + End Transition Enabled + + + Whether or not the end transition will play when emitted images reach the end of their lifetime. + + + endTransitionEnabledCheckBox + + + System.Windows.Forms.CheckBox, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + configurationGroupBox + + + 5 + + + False + + + True + + + NoControl + + + 6, 29 + + + 6, 0, 6, 0 + + + 59, 13 + + + 0 + + + Start Alpha + + + MiddleLeft + + + The alpha value that this transition will start from. This value can be less than 0 to delay the start of the transition. + + + startAlphaLabel + + + System.Windows.Forms.Label, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + configurationGroupBox + + + 6 + + + Top, Left, Right + + + 77, 27 + + + 6, 6, 6, 6 + + + 70, 20 + + + 1 + + + startAlphaNumericUpDownWheel + + + Natsnudasoft.EgamiFlowScreensaver.NumericUpDownWheel, Natsnudasoft.EgamiFlowScreensaver, Version=0.1.0.0, Culture=neutral, PublicKeyToken=null + + + configurationGroupBox + + + 7 + + + False + + + True + + + NoControl + + + 6, 61 + + + 6, 0, 6, 0 + + + 56, 13 + + + 2 + + + End Alpha + + + MiddleLeft + + + The alpha value that this transition will finish at. + + + endAlphaLabel + + + System.Windows.Forms.Label, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + configurationGroupBox + + + 8 + + + Top, Left, Right + + + 74, 59 + + + 6, 6, 6, 6 + + + 70, 20 + + + 3 + + + endAlphaNumericUpDownWheel + + + Natsnudasoft.EgamiFlowScreensaver.NumericUpDownWheel, Natsnudasoft.EgamiFlowScreensaver, Version=0.1.0.0, Culture=neutral, PublicKeyToken=null + + + configurationGroupBox + + + 9 + + + False + + + True + + + NoControl + + + 6, 93 + + + 6, 0, 6, 0 + + + 79, 13 + + + 4 + + + Transition Time + + + MiddleLeft + + + The amount of time (in milliseconds) that this transition will take. + + + transitionTimeLabel + + + System.Windows.Forms.Label, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + configurationGroupBox + + + 10 + + + Top, Left, Right + + + 97, 91 + + + 6, 6, 6, 6 + + + 114, 20 + + + 5 + + + transitionTimeNumericUpDownWheel + + + Natsnudasoft.EgamiFlowScreensaver.NumericUpDownWheel, Natsnudasoft.EgamiFlowScreensaver, Version=0.1.0.0, Culture=neutral, PublicKeyToken=null + + + configurationGroupBox + + + 11 + + + 12, 12 + + + 4, 4, 4, 4 + + + 0, 8, 0, 0 + + + 217, 227 + + + 0 + + + Configuration + + + configurationGroupBox + + + System.Windows.Forms.GroupBox, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + panel1 + + + 0 + + + False + + + Fill + + + 0, 0 + + + 0, 0, 0, 0 + + + 8, 8, 8, 8 + + + 241, 252 + + + 7 + + + panel1 + + + System.Windows.Forms.Panel, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + $this + + + 0 + + + True + + + 6, 13 + + + 369, 252 + + + CenterParent + + + Alpha Change Behaviour Configuration + + + configurationToolTip + + + System.Windows.Forms.ToolTip, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + AlphaChangeBehaviorForm + + + System.Windows.Forms.Form, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/src/EgamiFlowScreensaver/Config/AlphaChangeBehaviorFormViewModel.cs b/src/EgamiFlowScreensaver/Config/AlphaChangeBehaviorFormViewModel.cs new file mode 100644 index 0000000..02e4485 --- /dev/null +++ b/src/EgamiFlowScreensaver/Config/AlphaChangeBehaviorFormViewModel.cs @@ -0,0 +1,184 @@ +// +// Copyright (c) Adrian John Dunstan. All rights reserved. +// +// Licensed 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. +// + +namespace Natsnudasoft.EgamiFlowScreensaver.Config +{ + using System; + using System.Windows.Forms; + using Natsnudasoft.EgamiFlowScreensaver.Properties; + using Natsnudasoft.NatsnudaLibrary; + + /// + /// Provides a class for managing the state of the backing values for the current state of + /// configuration for an alpha change behaviour. + /// + /// + public sealed class AlphaChangeBehaviorFormViewModel : ConfigurationBehaviorFormViewModel + { + private readonly ILifetimeDetails lifetimeDetails; + private double transitionTime; + private float startAlpha; + private float endAlpha; + private bool endTransitionEnabled; + private float endTransitionAlpha; + private double endTransitionTime; + + /// + /// Initializes a new instance of the class. + /// + /// The current lifetime settings of any images emitted. + /// + /// is + /// . + public AlphaChangeBehaviorFormViewModel(ILifetimeDetails lifetimeDetails) + { + ParameterValidation.IsNotNull(lifetimeDetails, nameof(lifetimeDetails)); + + this.lifetimeDetails = lifetimeDetails; + } + + /// + /// Gets or sets the alpha value that the behaviour should start from. + /// + public float StartAlpha + { + get => this.startAlpha; + set => this.Set(ref this.startAlpha, value); + } + + /// + /// Gets or sets the alpha value that the behaviour should finish at. + /// + public float EndAlpha + { + get => this.endAlpha; + set => this.Set(ref this.endAlpha, value); + } + + /// + /// Gets or sets the time that the behaviour transition should take (in milliseconds). + /// + public double TransitionTime + { + get => this.transitionTime; + set => this.Set(ref this.transitionTime, value); + } + + /// + /// Gets a value indicating whether or not images will be emitted infinitely. + /// + public bool IsInfiniteImageEmitMode + { + get => this.lifetimeDetails.IsInfiniteImageEmitMode; + } + + /// + /// Gets or sets a value indicating whether or not the ending transition will be enabled for + /// the image item this behaviour is attached to. + /// + /// if the ending transition will be enabled for the image + /// item this behaviour is attached to; otherwise . + public bool EndTransitionEnabled + { + get => this.endTransitionEnabled; + set => this.Set(ref this.endTransitionEnabled, value && this.IsInfiniteImageEmitMode); + } + + /// + /// Gets or sets the alpha value that the behaviour will finish at when the image item + /// it is attached to is being destroyed. + /// + public float EndTransitionAlpha + { + get => this.endTransitionAlpha; + set => this.Set(ref this.endTransitionAlpha, value); + } + + /// + /// Gets or sets the time that the behaviour will take to transition when the image item + /// it is attached to is being destroyed (in milliseconds). + /// + public double EndTransitionTime + { + get => this.endTransitionTime; + set => this.Set(ref this.endTransitionTime, value); + } + + /// + /// is + /// . + /// The specified behaviour is not a valid + /// . + public override void UpdateFromBehavior(ConfigurationBehavior behavior) + { + ParameterValidation.IsNotNull(behavior, nameof(behavior)); + + if (behavior is AlphaChangeConfigurationBehavior alphaChangeConfigurationBehavior) + { + this.StartAlpha = alphaChangeConfigurationBehavior.StartAlpha; + this.EndAlpha = alphaChangeConfigurationBehavior.EndAlpha; + this.TransitionTime = + alphaChangeConfigurationBehavior.TransitionTime.TotalMilliseconds; + this.EndTransitionEnabled = alphaChangeConfigurationBehavior.EndTransitionEnabled; + this.EndTransitionAlpha = alphaChangeConfigurationBehavior.EndTransitionAlpha; + this.EndTransitionTime = + alphaChangeConfigurationBehavior.EndTransitionTime.TotalMilliseconds; + } + else + { + throw new InvalidOperationException(nameof(behavior) + " was an unexpected type."); + } + } + + /// + public override ConfigurationBehavior CreateBehavior() + { + return new AlphaChangeConfigurationBehavior + { + StartAlpha = this.StartAlpha, + EndAlpha = this.EndAlpha, + TransitionTime = + new TimeSpan((long)(this.TransitionTime * TimeSpan.TicksPerMillisecond)), + EndTransitionEnabled = this.EndTransitionEnabled, + EndTransitionAlpha = this.EndTransitionAlpha, + EndTransitionTime = + new TimeSpan((long)(this.EndTransitionTime * TimeSpan.TicksPerMillisecond)) + }; + } + + /// + public override bool Validate(IWin32Window owner) + { + var validated = true; + if (this.IsInfiniteImageEmitMode && + this.TransitionTime > this.lifetimeDetails.ImageEmitLifetime) + { + if (MessageBox.Show( + owner, + Resources.TransitionTimeLessThanLifetimeText, + Resources.TransitionTimeLessThanLifetimeCaption, + MessageBoxButtons.YesNo, + MessageBoxIcon.Question, + MessageBoxDefaultButton.Button2) == DialogResult.No) + { + validated = false; + } + } + + return validated; + } + } +} \ No newline at end of file diff --git a/src/EgamiFlowScreensaver/Config/AlphaChangeConfigurationBehavior.cs b/src/EgamiFlowScreensaver/Config/AlphaChangeConfigurationBehavior.cs new file mode 100644 index 0000000..b852dd2 --- /dev/null +++ b/src/EgamiFlowScreensaver/Config/AlphaChangeConfigurationBehavior.cs @@ -0,0 +1,103 @@ +// +// Copyright (c) Adrian John Dunstan. All rights reserved. +// +// Licensed 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. +// + +namespace Natsnudasoft.EgamiFlowScreensaver.Config +{ + using System; + using System.ComponentModel; + using Natsnudasoft.EgamiFlowScreensaver.Properties; + using ProtoBuf; + + /// + /// Provides a class for managing the current state of configuration for an alpha change + /// behaviour. + /// + /// + [ProtoContract] + public sealed class AlphaChangeConfigurationBehavior : ConfigurationBehavior + { + private const long DefaultTransitionTime = 1500 * TimeSpan.TicksPerMillisecond; + private const long DefaultEndTransitionTime = 250 * TimeSpan.TicksPerMillisecond; + + /// + public override ConfigurationBehaviorType ConfigurationBehaviorType + { + get => ConfigurationBehaviorType.AlphaChange; + } + + /// + public override string Description + { + get => Resources.AlphaChangeBehaviorDescription; + } + + /// + /// Gets or sets the alpha value that the behaviour should start from. + /// + [ProtoMember(1)] + public float StartAlpha { get; set; } + + /// + /// Gets or sets the alpha value that the behaviour should end at. + /// + [ProtoMember(2)] + [DefaultValue(1f)] + public float EndAlpha { get; set; } = 1f; + + /// + /// Gets or sets the time that the behaviour transition should take. + /// + public TimeSpan TransitionTime { get; set; } = new TimeSpan(DefaultTransitionTime); + + /// + /// Gets or sets a value indicating whether or not the ending transition will be enabled for + /// the image item this behaviour is attached to. + /// + /// if the ending transition will be enabled for the image + /// item this behaviour is attached to; otherwise . + [ProtoMember(4, IsRequired = false)] + public bool EndTransitionEnabled { get; set; } + + /// + /// Gets or sets the alpha value that the behaviour will finish at when the image item + /// it is attached to is being destroyed. + /// + [ProtoMember(5, IsRequired = false)] + public float EndTransitionAlpha { get; set; } + + /// + /// Gets or sets the time that the behaviour will take to transition when the image item + /// it is attached to is being destroyed. + /// + public TimeSpan EndTransitionTime { get; set; } = new TimeSpan(DefaultEndTransitionTime); + + [ProtoMember(3)] + [DefaultValue(DefaultTransitionTime)] + private long TransitionTimeSerialized + { + get => this.TransitionTime.Ticks; + set => this.TransitionTime = new TimeSpan(value); + } + + [ProtoMember(6, IsRequired = false)] + [DefaultValue(DefaultEndTransitionTime)] + private long EndTransitionTimeSerialized + { + get => this.EndTransitionTime.Ticks; + set => this.EndTransitionTime = new TimeSpan(value); + } + } +} \ No newline at end of file diff --git a/src/EgamiFlowScreensaver/Config/ApplyBehaviorsForm.Designer.cs b/src/EgamiFlowScreensaver/Config/ApplyBehaviorsForm.Designer.cs new file mode 100644 index 0000000..f034363 --- /dev/null +++ b/src/EgamiFlowScreensaver/Config/ApplyBehaviorsForm.Designer.cs @@ -0,0 +1,142 @@ +namespace Natsnudasoft.EgamiFlowScreensaver.Config +{ + partial class ApplyBehaviorsForm + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Windows Form Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage( + "Microsoft.Reliability", + "CA2000:Dispose objects before losing scope", + Justification = "This is auto generated code.")] + private void InitializeComponent() + { + this.components = new System.ComponentModel.Container(); + System.Windows.Forms.Panel panel2; + System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(ApplyBehaviorsForm)); + System.Windows.Forms.GroupBox behaviorsGroupBox; + System.Windows.Forms.Panel panel1; + this.okButton = new System.Windows.Forms.Button(); + this.cancelButton = new System.Windows.Forms.Button(); + this.behaviorDescriptionLabel = new System.Windows.Forms.Label(); + this.configureSelectedBehaviorButton = new System.Windows.Forms.Button(); + this.availableBehaviorsCheckedListBox = new System.Windows.Forms.CheckedListBox(); + this.configurationToolTip = new System.Windows.Forms.ToolTip(this.components); + panel2 = new System.Windows.Forms.Panel(); + behaviorsGroupBox = new System.Windows.Forms.GroupBox(); + panel1 = new System.Windows.Forms.Panel(); + panel2.SuspendLayout(); + behaviorsGroupBox.SuspendLayout(); + panel1.SuspendLayout(); + this.SuspendLayout(); + // + // panel2 + // + panel2.Controls.Add(this.okButton); + panel2.Controls.Add(this.cancelButton); + resources.ApplyResources(panel2, "panel2"); + panel2.Name = "panel2"; + // + // okButton + // + resources.ApplyResources(this.okButton, "okButton"); + this.okButton.Name = "okButton"; + this.configurationToolTip.SetToolTip(this.okButton, resources.GetString("okButton.ToolTip")); + this.okButton.UseVisualStyleBackColor = true; + // + // cancelButton + // + resources.ApplyResources(this.cancelButton, "cancelButton"); + this.cancelButton.DialogResult = System.Windows.Forms.DialogResult.Cancel; + this.cancelButton.Name = "cancelButton"; + this.configurationToolTip.SetToolTip(this.cancelButton, resources.GetString("cancelButton.ToolTip")); + this.cancelButton.UseVisualStyleBackColor = true; + // + // behaviorsGroupBox + // + resources.ApplyResources(behaviorsGroupBox, "behaviorsGroupBox"); + behaviorsGroupBox.Controls.Add(this.behaviorDescriptionLabel); + behaviorsGroupBox.Controls.Add(this.configureSelectedBehaviorButton); + behaviorsGroupBox.Controls.Add(this.availableBehaviorsCheckedListBox); + behaviorsGroupBox.Name = "behaviorsGroupBox"; + behaviorsGroupBox.TabStop = false; + // + // behaviorDescriptionLabel + // + resources.ApplyResources(this.behaviorDescriptionLabel, "behaviorDescriptionLabel"); + this.behaviorDescriptionLabel.BorderStyle = System.Windows.Forms.BorderStyle.Fixed3D; + this.behaviorDescriptionLabel.Name = "behaviorDescriptionLabel"; + this.configurationToolTip.SetToolTip(this.behaviorDescriptionLabel, resources.GetString("behaviorDescriptionLabel.ToolTip")); + // + // configureSelectedBehaviorButton + // + resources.ApplyResources(this.configureSelectedBehaviorButton, "configureSelectedBehaviorButton"); + this.configureSelectedBehaviorButton.Name = "configureSelectedBehaviorButton"; + this.configurationToolTip.SetToolTip(this.configureSelectedBehaviorButton, resources.GetString("configureSelectedBehaviorButton.ToolTip")); + this.configureSelectedBehaviorButton.UseVisualStyleBackColor = true; + // + // availableBehaviorsCheckedListBox + // + resources.ApplyResources(this.availableBehaviorsCheckedListBox, "availableBehaviorsCheckedListBox"); + this.availableBehaviorsCheckedListBox.FormattingEnabled = true; + this.availableBehaviorsCheckedListBox.Name = "availableBehaviorsCheckedListBox"; + this.configurationToolTip.SetToolTip(this.availableBehaviorsCheckedListBox, resources.GetString("availableBehaviorsCheckedListBox.ToolTip")); + // + // panel1 + // + panel1.Controls.Add(behaviorsGroupBox); + resources.ApplyResources(panel1, "panel1"); + panel1.Name = "panel1"; + // + // ApplyBehaviorsForm + // + this.AcceptButton = this.okButton; + resources.ApplyResources(this, "$this"); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.CancelButton = this.cancelButton; + this.Controls.Add(panel1); + this.Controls.Add(panel2); + this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedDialog; + this.MaximizeBox = false; + this.MinimizeBox = false; + this.Name = "ApplyBehaviorsForm"; + this.ShowIcon = false; + this.ShowInTaskbar = false; + this.SizeGripStyle = System.Windows.Forms.SizeGripStyle.Hide; + panel2.ResumeLayout(false); + behaviorsGroupBox.ResumeLayout(false); + panel1.ResumeLayout(false); + this.ResumeLayout(false); + + } + + #endregion + private System.Windows.Forms.ToolTip configurationToolTip; + private System.Windows.Forms.Button okButton; + private System.Windows.Forms.Button cancelButton; + private System.Windows.Forms.CheckedListBox availableBehaviorsCheckedListBox; + private System.Windows.Forms.Button configureSelectedBehaviorButton; + private System.Windows.Forms.Label behaviorDescriptionLabel; + } +} \ No newline at end of file diff --git a/src/EgamiFlowScreensaver/Config/ApplyBehaviorsForm.cs b/src/EgamiFlowScreensaver/Config/ApplyBehaviorsForm.cs new file mode 100644 index 0000000..0e7f029 --- /dev/null +++ b/src/EgamiFlowScreensaver/Config/ApplyBehaviorsForm.cs @@ -0,0 +1,135 @@ +// +// Copyright (c) Adrian John Dunstan. All rights reserved. +// +// Licensed 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. +// + +namespace Natsnudasoft.EgamiFlowScreensaver.Config +{ + using System; + using System.Collections.Generic; + using System.Linq; + using System.Windows.Forms; + using Natsnudasoft.NatsnudaLibrary; + + /// + /// A configuration form to allow the application and configuration of various behaviours that + /// will be applied to image items. + /// + /// + public partial class ApplyBehaviorsForm : Form + { + private readonly ApplyBehaviorsFormViewModel viewModel; + + /// + /// Initializes a new instance of the class. + /// + /// The view model to bind to. + /// is + /// . + public ApplyBehaviorsForm(ApplyBehaviorsFormViewModel viewModel) + { + ParameterValidation.IsNotNull(viewModel, nameof(viewModel)); + + this.viewModel = viewModel; + this.InitializeComponent(); + + var imageItemBehaviorTypeSource = Enum + .GetValues(typeof(ConfigurationBehaviorType)) + .Cast() + .Select(m => new DataSourceDisplayValue( + EnumHelper.GetEnumDisplayName(m), m)) + .ToArray(); + this.availableBehaviorsCheckedListBox.DataSource = imageItemBehaviorTypeSource; + this.availableBehaviorsCheckedListBox.DisplayMember = + nameof(DataSourceDisplayValue.DisplayValue); + this.availableBehaviorsCheckedListBox.ValueMember = + nameof(DataSourceDisplayValue.Value); + this.availableBehaviorsCheckedListBox.DataBindings.Add( + nameof(CheckedListBox.SelectedValue), + this.viewModel, + nameof(ApplyBehaviorsFormViewModel.SelectedBehaviorType), + true, + DataSourceUpdateMode.OnPropertyChanged); + this.UpdateCheckedBehaviors(); + this.UpdateCanConfigureSelectedBehavior(); + this.availableBehaviorsCheckedListBox.ItemCheck += (sender, e) => + { + this.BeginInvoke((MethodInvoker)this.UpdateCanConfigureSelectedBehavior); + var setEnabledBehaviors = + (Action>)this.viewModel + .SetEnabledBehaviors; + var enabledBehaviorTypes = this.availableBehaviorsCheckedListBox.CheckedItems + .Cast>() + .Select(b => b.Value); + this.BeginInvoke(setEnabledBehaviors, enabledBehaviorTypes); + }; + this.availableBehaviorsCheckedListBox.SelectedIndexChanged += (sender, e) => + { + this.UpdateCanConfigureSelectedBehavior(); + }; + + this.behaviorDescriptionLabel.DataBindings.Add( + nameof(Label.Text), + this.viewModel, + nameof(ApplyBehaviorsFormViewModel.SelectedBehaviorDescription)); + this.viewModel.UpdateBehaviorDescription(); + + this.configureSelectedBehaviorButton.DataBindings.Add( + nameof(Button.Enabled), + this.viewModel, + nameof(ApplyBehaviorsFormViewModel.CanConfigureSelectedBehavior)); + this.configureSelectedBehaviorButton.Click += (sender, e) => + { + this.viewModel.ConfigureBehavior(this); + }; + + this.okButton.Click += (sender, e) => + { + this.okButton.Focus(); + this.DialogResult = DialogResult.OK; + }; + } + + private void UpdateCheckedBehaviors() + { + var enabledBehaviors = this.viewModel.EnabledBehaviorTypes.ToHashSet(); + var behaviorTypesWithIndex = this.availableBehaviorsCheckedListBox.Items + .Cast>() + .Select((b, i) => new { BehaviorType = b.Value, Index = i }) + .ToArray(); + foreach (var behaviorTypeWithIndex in behaviorTypesWithIndex) + { + if (enabledBehaviors.Contains(behaviorTypeWithIndex.BehaviorType)) + { + this.availableBehaviorsCheckedListBox.SetItemChecked( + behaviorTypeWithIndex.Index, + true); + } + } + } + + private void UpdateCanConfigureSelectedBehavior() + { + var index = this.availableBehaviorsCheckedListBox.SelectedIndex; + var canConfigureSelectedBehavior = false; + if (index >= 0 && index <= this.availableBehaviorsCheckedListBox.Items.Count) + { + canConfigureSelectedBehavior = + this.availableBehaviorsCheckedListBox.GetItemChecked(index); + } + + this.viewModel.CanConfigureSelectedBehavior = canConfigureSelectedBehavior; + } + } +} \ No newline at end of file diff --git a/src/EgamiFlowScreensaver/Config/ApplyBehaviorsForm.resx b/src/EgamiFlowScreensaver/Config/ApplyBehaviorsForm.resx new file mode 100644 index 0000000..1905e0a --- /dev/null +++ b/src/EgamiFlowScreensaver/Config/ApplyBehaviorsForm.resx @@ -0,0 +1,423 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + False + + + + Top, Left, Right + + + NoControl + + + + 14, 15 + + + 6, 6, 6, 6 + + + 99, 26 + + + + 0 + + + OK + + + 17, 17 + + + Save the current behaviour settings. + + + okButton + + + System.Windows.Forms.Button, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + panel2 + + + 0 + + + Top, Left, Right + + + NoControl + + + 14, 53 + + + 6, 6, 6, 6 + + + 99, 26 + + + 1 + + + Cancel + + + Close the behaviours dialog without saving. + + + cancelButton + + + System.Windows.Forms.Button, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + panel2 + + + 1 + + + Right + + + 311, 0 + + + 0, 0, 0, 0 + + + 8, 8, 8, 8 + + + 128, 343 + + + 4 + + + panel2 + + + System.Windows.Forms.Panel, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + $this + + + 1 + + + False + + + Top, Bottom, Left, Right + + + Top, Bottom, Left, Right + + + 12, 186 + + + 12, 6, 12, 6 + + + 263, 126 + + + 2 + + + The description of the currently selected behaviour. + + + behaviorDescriptionLabel + + + System.Windows.Forms.Label, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + behaviorsGroupBox + + + 0 + + + Bottom, Right + + + NoControl + + + 134, 148 + + + 6, 6, 6, 6 + + + 141, 26 + + + 1 + + + Configure Behaviour + + + Configure the selected behaviour. + + + configureSelectedBehaviorButton + + + System.Windows.Forms.Button, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + behaviorsGroupBox + + + 1 + + + Top, Left, Right + + + 12, 27 + + + 12, 6, 12, 6 + + + 263, 109 + + + 0 + + + A list of available behaviours to apply to emitted images. + + + availableBehaviorsCheckedListBox + + + System.Windows.Forms.CheckedListBox, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + behaviorsGroupBox + + + 2 + + + 12, 12 + + + 4, 4, 4, 4 + + + 0, 8, 0, 0 + + + 287, 318 + + + 0 + + + Behaviours + + + behaviorsGroupBox + + + System.Windows.Forms.GroupBox, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + panel1 + + + 0 + + + False + + + Fill + + + 0, 0 + + + 0, 0, 0, 0 + + + 8, 8, 8, 8 + + + 311, 343 + + + 3 + + + panel1 + + + System.Windows.Forms.Panel, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + $this + + + 0 + + + True + + + 6, 13 + + + 439, 343 + + + CenterParent + + + Egami Flow Screensaver Apply Behaviours + + + configurationToolTip + + + System.Windows.Forms.ToolTip, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + ApplyBehaviorsForm + + + System.Windows.Forms.Form, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/src/EgamiFlowScreensaver/Config/ApplyBehaviorsFormViewModel.cs b/src/EgamiFlowScreensaver/Config/ApplyBehaviorsFormViewModel.cs new file mode 100644 index 0000000..6aa806f --- /dev/null +++ b/src/EgamiFlowScreensaver/Config/ApplyBehaviorsFormViewModel.cs @@ -0,0 +1,226 @@ +// +// Copyright (c) Adrian John Dunstan. All rights reserved. +// +// Licensed 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. +// + +namespace Natsnudasoft.EgamiFlowScreensaver.Config +{ + using System; + using System.Collections.Generic; + using System.Linq; + using System.Windows.Forms; + using Natsnudasoft.EgamiFlowScreensaver.Properties; + using Natsnudasoft.NatsnudaLibrary; + + /// + /// Provides a class for managing the state of the backing values for the current state of + /// configuration for applying behaviours to a screensaver. + /// + /// + public sealed class ApplyBehaviorsFormViewModel : ObservableBase + { + private readonly IBehaviorConfigurationFactory behaviorConfigurationFactory; + private readonly IBehaviorConfigurationFormFactory behaviorConfigurationFormFactory; + private readonly ILifetimeDetails lifetimeDetails; + private readonly Dictionary behaviors; + private readonly HashSet enabledBehaviorTypes; + + private ConfigurationBehaviorType selectedBehaviorType; + private string selectedBehaviorDescription; + private bool canConfigureSelectedBehavior; + + /// + /// Initializes a new instance of the class. + /// + /// A collection of existing behaviour configurations that this + /// view model will manage. + /// A factory to be used to create any behaviour + /// configurations needed by this view model. + /// A factory to be used to create any + /// behaviour configuration forms needed by this view model. + /// The current lifetime settings of any images emitted. + /// + /// , + /// , or + /// is . + public ApplyBehaviorsFormViewModel( + IEnumerable behaviors, + IBehaviorConfigurationFactory behaviorConfigurationFactory, + IBehaviorConfigurationFormFactory behaviorConfigurationFormFactory, + ILifetimeDetails lifetimeDetails) + { + ParameterValidation.IsNotNull( + behaviorConfigurationFactory, + nameof(behaviorConfigurationFactory)); + ParameterValidation.IsNotNull( + behaviorConfigurationFormFactory, + nameof(behaviorConfigurationFormFactory)); + ParameterValidation.IsNotNull(lifetimeDetails, nameof(lifetimeDetails)); + + this.behaviorConfigurationFactory = behaviorConfigurationFactory; + this.behaviorConfigurationFormFactory = behaviorConfigurationFormFactory; + this.lifetimeDetails = lifetimeDetails; + this.behaviors = behaviors.ToDictionary(b => b.ConfigurationBehaviorType); + this.enabledBehaviorTypes = new HashSet(behaviors + .Where(b => b.Enabled) + .Select(b => b.ConfigurationBehaviorType)); + } + + /// + /// Gets or sets the currently selected behaviour type. + /// + public ConfigurationBehaviorType SelectedBehaviorType + { + get => this.selectedBehaviorType; + set + { + this.Set(ref this.selectedBehaviorType, value); + this.UpdateBehaviorDescription(); + } + } + + /// + /// Gets or sets the description of the currently selected behaviour type. + /// + public string SelectedBehaviorDescription + { + get => this.selectedBehaviorDescription; + set => this.Set(ref this.selectedBehaviorDescription, value); + } + + /// + /// Gets or sets a value indicating whether the currently selected behaviour can be + /// configured. + /// + public bool CanConfigureSelectedBehavior + { + get => this.canConfigureSelectedBehavior; + set => this.Set(ref this.canConfigureSelectedBehavior, value); + } + + /// + /// Gets a collection of behaviours that this view model is managing. + /// + public IReadOnlyCollection Behaviors + { + get => this.behaviors.Values; + } + + /// + /// Gets a list of behaviours that have been enabled in this view models lifetime. + /// + public IReadOnlyCollection EnabledBehaviorTypes + { + get => this.enabledBehaviorTypes; + } + + /// + /// Updates the currently selected behaviour description based on the currently selected + /// behaviour. + /// + public void UpdateBehaviorDescription() + { + var behavior = this.GetOrCreateBehavior(this.SelectedBehaviorType); + this.SelectedBehaviorDescription = behavior.Description; + } + + /// + /// Sets which behaviours are currently enabled based on a collection of behaviour types. + /// + /// A collection of behaviour types that are currently + /// enabled. + /// is + /// . + public void SetEnabledBehaviors( + IEnumerable newEnabledBehaviorTypes) + { + ParameterValidation.IsNotNull(newEnabledBehaviorTypes, nameof(newEnabledBehaviorTypes)); + + this.enabledBehaviorTypes.Clear(); + foreach (var behaviorType in newEnabledBehaviorTypes) + { + this.GetOrCreateBehavior(behaviorType); + this.enabledBehaviorTypes.Add(behaviorType); + } + } + + /// + /// Synchronizes the enabled state of underlying behaviour configurations with the + /// behaviours that have been enabled in this view model. + /// + public void SynchronizeEnabledBehaviors() + { + foreach (var behavior in this.Behaviors) + { + behavior.Enabled = false; + } + + foreach (var behaviorType in this.enabledBehaviorTypes) + { + var behavior = this.GetOrCreateBehavior(behaviorType); + behavior.Enabled = true; + } + } + + /// + /// Displays a dialog allowing the selected behaviour to be configured if a configuration + /// form is available. + /// + /// The window that will own any dialogs that will be displayed. + /// is + /// . + public void ConfigureBehavior(IWin32Window owner) + { + ParameterValidation.IsNotNull(owner, nameof(owner)); + + var behaviorType = this.SelectedBehaviorType; + var behavior = this.GetOrCreateBehavior(behaviorType); + if (this.behaviorConfigurationFormFactory.TryCreate( + behaviorType, + this.lifetimeDetails, + out var behaviorForm, + out var behaviorFormViewModel)) + { + using (behaviorForm) + { + behaviorFormViewModel.UpdateFromBehavior(behavior); + if (behaviorForm.ShowDialog(owner) == DialogResult.OK) + { + this.behaviors[behaviorType] = behaviorFormViewModel.CreateBehavior(); + } + } + } + else + { + MessageBox.Show( + owner, + Resources.NoConfigurationFormForBehaviorText, + Resources.NoConfigurationFormForBehaviorCaption, + MessageBoxButtons.OK, + MessageBoxIcon.Information); + } + } + + private ConfigurationBehavior GetOrCreateBehavior(ConfigurationBehaviorType behaviorType) + { + if (!this.behaviors.TryGetValue(behaviorType, out var behavior)) + { + behavior = this.behaviorConfigurationFactory.Create(behaviorType); + this.behaviors[behaviorType] = behavior; + } + + return behavior; + } + } +} \ No newline at end of file diff --git a/src/EgamiFlowScreensaver/Config/BehaviorConfigurationFactory.cs b/src/EgamiFlowScreensaver/Config/BehaviorConfigurationFactory.cs new file mode 100644 index 0000000..d41dd42 --- /dev/null +++ b/src/EgamiFlowScreensaver/Config/BehaviorConfigurationFactory.cs @@ -0,0 +1,58 @@ +// +// Copyright (c) Adrian John Dunstan. All rights reserved. +// +// Licensed 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. +// + +namespace Natsnudasoft.EgamiFlowScreensaver.Config +{ + using System; + + /// + /// Provides a class capable of creating behaviour configurations based on a specified + /// . + /// + /// + /// + public sealed class BehaviorConfigurationFactory : IBehaviorConfigurationFactory + { + /// + /// No method exists for creating a behaviour + /// configuration from the value specified by . + public ConfigurationBehavior Create(ConfigurationBehaviorType behaviorType) + { + ConfigurationBehavior behavior; + switch (behaviorType) + { + case ConfigurationBehaviorType.ColorChange: + behavior = new ColorChangeConfigurationBehavior(); + break; + case ConfigurationBehaviorType.AlphaChange: + behavior = new AlphaChangeConfigurationBehavior(); + break; + case ConfigurationBehaviorType.ScaleChange: + behavior = new ScaleChangeConfigurationBehavior(); + break; + case ConfigurationBehaviorType.RotationChange: + behavior = new RotationChangeConfigurationBehavior(); + break; + default: + throw new InvalidOperationException("There is no behavior configuration " + + "associated with the specified behavior type " + + $"({behaviorType})."); + } + + return behavior; + } + } +} \ No newline at end of file diff --git a/src/EgamiFlowScreensaver/Config/BehaviorConfigurationFormFactory.cs b/src/EgamiFlowScreensaver/Config/BehaviorConfigurationFormFactory.cs new file mode 100644 index 0000000..24b86f2 --- /dev/null +++ b/src/EgamiFlowScreensaver/Config/BehaviorConfigurationFormFactory.cs @@ -0,0 +1,127 @@ +// +// Copyright (c) Adrian John Dunstan. All rights reserved. +// +// Licensed 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. +// + +namespace Natsnudasoft.EgamiFlowScreensaver.Config +{ + using System; + using System.Windows.Forms; + using Natsnudasoft.NatsnudaLibrary; + + /// + /// Provides a class capable of creating configuration forms and their associated view models + /// based on a specified . + /// + /// + /// + public class BehaviorConfigurationFormFactory : IBehaviorConfigurationFormFactory + { + /// + /// is + /// . + public bool TryCreate( + ConfigurationBehaviorType behaviorType, + ILifetimeDetails lifetimeDetails, + out Form behaviorForm, + out ConfigurationBehaviorFormViewModel behaviorFormViewModel) + { + ParameterValidation.IsNotNull(lifetimeDetails, nameof(lifetimeDetails)); + + bool result; + switch (behaviorType) + { + case ConfigurationBehaviorType.ColorChange: + CreateColorChangeForm( + lifetimeDetails, + out behaviorForm, + out behaviorFormViewModel); + result = true; + break; + case ConfigurationBehaviorType.ScaleChange: + CreateScaleChangeForm( + lifetimeDetails, + out behaviorForm, + out behaviorFormViewModel); + result = true; + break; + case ConfigurationBehaviorType.AlphaChange: + CreateAlphaChangeForm( + lifetimeDetails, + out behaviorForm, + out behaviorFormViewModel); + result = true; + break; + case ConfigurationBehaviorType.RotationChange: + CreateRotationChangeForm( + lifetimeDetails, + out behaviorForm, + out behaviorFormViewModel); + result = true; + break; + default: + behaviorFormViewModel = default; + behaviorForm = default; + result = false; + break; + } + + return result; + } + + private static void CreateColorChangeForm( + ILifetimeDetails lifetimeDetails, + out Form behaviorForm, + out ConfigurationBehaviorFormViewModel behaviorFormViewModel) + { + var colorChangeBehaviorFormViewModel = + new ColorChangeBehaviorFormViewModel(lifetimeDetails); + behaviorFormViewModel = colorChangeBehaviorFormViewModel; + behaviorForm = new ColorChangeBehaviorForm(colorChangeBehaviorFormViewModel); + } + + private static void CreateScaleChangeForm( + ILifetimeDetails lifetimeDetails, + out Form behaviorForm, + out ConfigurationBehaviorFormViewModel behaviorFormViewModel) + { + var scaleChangeBehaviorFormViewModel = + new ScaleChangeBehaviorFormViewModel(lifetimeDetails); + behaviorFormViewModel = scaleChangeBehaviorFormViewModel; + behaviorForm = new ScaleChangeBehaviorForm(scaleChangeBehaviorFormViewModel); + } + + private static void CreateAlphaChangeForm( + ILifetimeDetails lifetimeDetails, + out Form behaviorForm, + out ConfigurationBehaviorFormViewModel behaviorFormViewModel) + { + var alphaChangeBehaviorFormViewModel = + new AlphaChangeBehaviorFormViewModel(lifetimeDetails); + behaviorFormViewModel = alphaChangeBehaviorFormViewModel; + behaviorForm = new AlphaChangeBehaviorForm(alphaChangeBehaviorFormViewModel); + } + + private static void CreateRotationChangeForm( + ILifetimeDetails lifetimeDetails, + out Form behaviorForm, + out ConfigurationBehaviorFormViewModel behaviorFormViewModel) + { + var rotationChangeBehaviorFormViewModel = + new RotationChangeBehaviorFormViewModel(lifetimeDetails); + behaviorFormViewModel = rotationChangeBehaviorFormViewModel; + behaviorForm = new RotationChangeBehaviorForm(rotationChangeBehaviorFormViewModel); + } + } +} \ No newline at end of file diff --git a/src/EgamiFlowScreensaver/Config/ColorChangeBehaviorForm.Designer.cs b/src/EgamiFlowScreensaver/Config/ColorChangeBehaviorForm.Designer.cs new file mode 100644 index 0000000..6591c4d --- /dev/null +++ b/src/EgamiFlowScreensaver/Config/ColorChangeBehaviorForm.Designer.cs @@ -0,0 +1,223 @@ +namespace Natsnudasoft.EgamiFlowScreensaver.Config +{ + partial class ColorChangeBehaviorForm + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Windows Form Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage( + "Microsoft.Reliability", + "CA2000:Dispose objects before losing scope", + Justification = "This is auto generated code.")] + private void InitializeComponent() + { + this.components = new System.ComponentModel.Container(); + System.Windows.Forms.Panel panel2; + System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(ColorChangeBehaviorForm)); + System.Windows.Forms.GroupBox configurationGroupBox; + System.Windows.Forms.Label transitionTimeLabel; + System.Windows.Forms.Panel panel1; + this.okButton = new System.Windows.Forms.Button(); + this.cancelButton = new System.Windows.Forms.Button(); + this.endTransitionTimeLabel = new System.Windows.Forms.Label(); + this.endTransitionTimeNumericUpDownWheel = new Natsnudasoft.EgamiFlowScreensaver.NumericUpDownWheel(); + this.horizontalSplitterLabel = new System.Windows.Forms.Label(); + this.chooseEndTransitionColorButton = new System.Windows.Forms.Button(); + this.endTransitionEnabledCheckBox = new System.Windows.Forms.CheckBox(); + this.chooseEndColorButton = new System.Windows.Forms.Button(); + this.chooseStartColorButton = new System.Windows.Forms.Button(); + this.transitionTimeNumericUpDownWheel = new Natsnudasoft.EgamiFlowScreensaver.NumericUpDownWheel(); + this.configurationToolTip = new System.Windows.Forms.ToolTip(this.components); + panel2 = new System.Windows.Forms.Panel(); + configurationGroupBox = new System.Windows.Forms.GroupBox(); + transitionTimeLabel = new System.Windows.Forms.Label(); + panel1 = new System.Windows.Forms.Panel(); + panel2.SuspendLayout(); + configurationGroupBox.SuspendLayout(); + ((System.ComponentModel.ISupportInitialize)(this.endTransitionTimeNumericUpDownWheel)).BeginInit(); + ((System.ComponentModel.ISupportInitialize)(this.transitionTimeNumericUpDownWheel)).BeginInit(); + panel1.SuspendLayout(); + this.SuspendLayout(); + // + // panel2 + // + panel2.Controls.Add(this.okButton); + panel2.Controls.Add(this.cancelButton); + resources.ApplyResources(panel2, "panel2"); + panel2.Name = "panel2"; + // + // okButton + // + resources.ApplyResources(this.okButton, "okButton"); + this.okButton.Name = "okButton"; + this.configurationToolTip.SetToolTip(this.okButton, resources.GetString("okButton.ToolTip")); + this.okButton.UseVisualStyleBackColor = true; + // + // cancelButton + // + resources.ApplyResources(this.cancelButton, "cancelButton"); + this.cancelButton.DialogResult = System.Windows.Forms.DialogResult.Cancel; + this.cancelButton.Name = "cancelButton"; + this.configurationToolTip.SetToolTip(this.cancelButton, resources.GetString("cancelButton.ToolTip")); + this.cancelButton.UseVisualStyleBackColor = true; + // + // configurationGroupBox + // + resources.ApplyResources(configurationGroupBox, "configurationGroupBox"); + configurationGroupBox.Controls.Add(this.endTransitionTimeLabel); + configurationGroupBox.Controls.Add(this.endTransitionTimeNumericUpDownWheel); + configurationGroupBox.Controls.Add(this.horizontalSplitterLabel); + configurationGroupBox.Controls.Add(this.chooseEndTransitionColorButton); + configurationGroupBox.Controls.Add(this.endTransitionEnabledCheckBox); + configurationGroupBox.Controls.Add(transitionTimeLabel); + configurationGroupBox.Controls.Add(this.chooseEndColorButton); + configurationGroupBox.Controls.Add(this.chooseStartColorButton); + configurationGroupBox.Controls.Add(this.transitionTimeNumericUpDownWheel); + configurationGroupBox.Name = "configurationGroupBox"; + configurationGroupBox.TabStop = false; + // + // endTransitionTimeLabel + // + resources.ApplyResources(this.endTransitionTimeLabel, "endTransitionTimeLabel"); + this.endTransitionTimeLabel.Name = "endTransitionTimeLabel"; + this.configurationToolTip.SetToolTip(this.endTransitionTimeLabel, resources.GetString("endTransitionTimeLabel.ToolTip")); + // + // endTransitionTimeNumericUpDownWheel + // + resources.ApplyResources(this.endTransitionTimeNumericUpDownWheel, "endTransitionTimeNumericUpDownWheel"); + this.endTransitionTimeNumericUpDownWheel.DecimalPlaces = 2; + this.endTransitionTimeNumericUpDownWheel.Increment = new decimal(new int[] { + 100, + 0, + 0, + 0}); + this.endTransitionTimeNumericUpDownWheel.Maximum = new decimal(new int[] { + 604800000, + 0, + 0, + 0}); + this.endTransitionTimeNumericUpDownWheel.Name = "endTransitionTimeNumericUpDownWheel"; + // + // horizontalSplitterLabel + // + resources.ApplyResources(this.horizontalSplitterLabel, "horizontalSplitterLabel"); + this.horizontalSplitterLabel.BorderStyle = System.Windows.Forms.BorderStyle.Fixed3D; + this.horizontalSplitterLabel.Name = "horizontalSplitterLabel"; + // + // chooseEndTransitionColorButton + // + resources.ApplyResources(this.chooseEndTransitionColorButton, "chooseEndTransitionColorButton"); + this.chooseEndTransitionColorButton.Name = "chooseEndTransitionColorButton"; + this.configurationToolTip.SetToolTip(this.chooseEndTransitionColorButton, resources.GetString("chooseEndTransitionColorButton.ToolTip")); + this.chooseEndTransitionColorButton.UseVisualStyleBackColor = true; + // + // endTransitionEnabledCheckBox + // + resources.ApplyResources(this.endTransitionEnabledCheckBox, "endTransitionEnabledCheckBox"); + this.endTransitionEnabledCheckBox.Name = "endTransitionEnabledCheckBox"; + this.configurationToolTip.SetToolTip(this.endTransitionEnabledCheckBox, resources.GetString("endTransitionEnabledCheckBox.ToolTip")); + this.endTransitionEnabledCheckBox.UseVisualStyleBackColor = true; + // + // transitionTimeLabel + // + resources.ApplyResources(transitionTimeLabel, "transitionTimeLabel"); + transitionTimeLabel.Name = "transitionTimeLabel"; + this.configurationToolTip.SetToolTip(transitionTimeLabel, resources.GetString("transitionTimeLabel.ToolTip")); + // + // chooseEndColorButton + // + resources.ApplyResources(this.chooseEndColorButton, "chooseEndColorButton"); + this.chooseEndColorButton.Name = "chooseEndColorButton"; + this.configurationToolTip.SetToolTip(this.chooseEndColorButton, resources.GetString("chooseEndColorButton.ToolTip")); + this.chooseEndColorButton.UseVisualStyleBackColor = true; + // + // chooseStartColorButton + // + resources.ApplyResources(this.chooseStartColorButton, "chooseStartColorButton"); + this.chooseStartColorButton.Name = "chooseStartColorButton"; + this.configurationToolTip.SetToolTip(this.chooseStartColorButton, resources.GetString("chooseStartColorButton.ToolTip")); + this.chooseStartColorButton.UseVisualStyleBackColor = true; + // + // transitionTimeNumericUpDownWheel + // + resources.ApplyResources(this.transitionTimeNumericUpDownWheel, "transitionTimeNumericUpDownWheel"); + this.transitionTimeNumericUpDownWheel.DecimalPlaces = 2; + this.transitionTimeNumericUpDownWheel.Increment = new decimal(new int[] { + 100, + 0, + 0, + 0}); + this.transitionTimeNumericUpDownWheel.Maximum = new decimal(new int[] { + 604800000, + 0, + 0, + 0}); + this.transitionTimeNumericUpDownWheel.Name = "transitionTimeNumericUpDownWheel"; + // + // panel1 + // + panel1.Controls.Add(configurationGroupBox); + resources.ApplyResources(panel1, "panel1"); + panel1.Name = "panel1"; + // + // ColorChangeBehaviorForm + // + this.AcceptButton = this.okButton; + resources.ApplyResources(this, "$this"); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.CancelButton = this.cancelButton; + this.Controls.Add(panel1); + this.Controls.Add(panel2); + this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedToolWindow; + this.MaximizeBox = false; + this.MinimizeBox = false; + this.Name = "ColorChangeBehaviorForm"; + this.ShowIcon = false; + this.ShowInTaskbar = false; + this.SizeGripStyle = System.Windows.Forms.SizeGripStyle.Hide; + panel2.ResumeLayout(false); + configurationGroupBox.ResumeLayout(false); + configurationGroupBox.PerformLayout(); + ((System.ComponentModel.ISupportInitialize)(this.endTransitionTimeNumericUpDownWheel)).EndInit(); + ((System.ComponentModel.ISupportInitialize)(this.transitionTimeNumericUpDownWheel)).EndInit(); + panel1.ResumeLayout(false); + this.ResumeLayout(false); + + } + + #endregion + + private System.Windows.Forms.Button okButton; + private System.Windows.Forms.ToolTip configurationToolTip; + private System.Windows.Forms.Button cancelButton; + private NumericUpDownWheel transitionTimeNumericUpDownWheel; + private System.Windows.Forms.Button chooseEndColorButton; + private System.Windows.Forms.Button chooseStartColorButton; + private System.Windows.Forms.CheckBox endTransitionEnabledCheckBox; + private System.Windows.Forms.Button chooseEndTransitionColorButton; + private System.Windows.Forms.Label horizontalSplitterLabel; + private NumericUpDownWheel endTransitionTimeNumericUpDownWheel; + private System.Windows.Forms.Label endTransitionTimeLabel; + } +} \ No newline at end of file diff --git a/src/EgamiFlowScreensaver/Config/ColorChangeBehaviorForm.cs b/src/EgamiFlowScreensaver/Config/ColorChangeBehaviorForm.cs new file mode 100644 index 0000000..1314ea6 --- /dev/null +++ b/src/EgamiFlowScreensaver/Config/ColorChangeBehaviorForm.cs @@ -0,0 +1,106 @@ +// +// Copyright (c) Adrian John Dunstan. All rights reserved. +// +// Licensed 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. +// + +namespace Natsnudasoft.EgamiFlowScreensaver.Config +{ + using System; + using System.Windows.Forms; + using Natsnudasoft.NatsnudaLibrary; + + /// + /// A form allowing configuration of a behaviour that causes an image item to transition from + /// one colour value to another over a specified time frame. + /// + /// + public partial class ColorChangeBehaviorForm : Form + { + private readonly ColorChangeBehaviorFormViewModel viewModel; + + /// + /// Initializes a new instance of the class. + /// + /// The view model to bind to. + /// is + /// . + public ColorChangeBehaviorForm(ColorChangeBehaviorFormViewModel viewModel) + { + ParameterValidation.IsNotNull(viewModel, nameof(viewModel)); + + this.viewModel = viewModel; + this.InitializeComponent(); + + this.chooseStartColorButton.Click += (sender, e) => + { + this.viewModel.ChooseStartColor(this); + }; + + this.chooseEndColorButton.Click += (sender, e) => + { + this.viewModel.ChooseEndColor(this); + }; + + this.transitionTimeNumericUpDownWheel.DataBindings.Add( + nameof(NumericUpDown.Value), + this.viewModel, + nameof(ColorChangeBehaviorFormViewModel.TransitionTime), + true); + + this.endTransitionEnabledCheckBox.DataBindings.Add( + nameof(CheckBox.Enabled), + this.viewModel, + nameof(ColorChangeBehaviorFormViewModel.IsInfiniteImageEmitMode)); + this.endTransitionEnabledCheckBox.DataBindings.Add( + nameof(CheckBox.Checked), + this.viewModel, + nameof(ColorChangeBehaviorFormViewModel.EndTransitionEnabled), + false, + DataSourceUpdateMode.OnPropertyChanged); + + this.chooseEndTransitionColorButton.DataBindings.Add( + nameof(Button.Enabled), + this.viewModel, + nameof(ColorChangeBehaviorFormViewModel.EndTransitionEnabled)); + this.chooseEndTransitionColorButton.Click += (sender, e) => + { + this.viewModel.ChooseEndTransitionColor(this); + }; + + this.endTransitionTimeLabel.DataBindings.Add( + nameof(Label.Enabled), + this.viewModel, + nameof(ColorChangeBehaviorFormViewModel.EndTransitionEnabled)); + + this.endTransitionTimeNumericUpDownWheel.DataBindings.Add( + nameof(NumericUpDownWheel.Enabled), + this.viewModel, + nameof(ColorChangeBehaviorFormViewModel.EndTransitionEnabled)); + this.endTransitionTimeNumericUpDownWheel.DataBindings.Add( + nameof(NumericUpDownWheel.Value), + this.viewModel, + nameof(ColorChangeBehaviorFormViewModel.EndTransitionTime), + true); + + this.okButton.Click += (sender, e) => + { + if (this.viewModel.Validate(this)) + { + this.okButton.Focus(); + this.DialogResult = DialogResult.OK; + } + }; + } + } +} \ No newline at end of file diff --git a/src/EgamiFlowScreensaver/Config/ColorChangeBehaviorForm.resx b/src/EgamiFlowScreensaver/Config/ColorChangeBehaviorForm.resx new file mode 100644 index 0000000..3a3b3d5 --- /dev/null +++ b/src/EgamiFlowScreensaver/Config/ColorChangeBehaviorForm.resx @@ -0,0 +1,660 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + False + + + okButton + + + System.Windows.Forms.Button, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + panel2 + + + 0 + + + cancelButton + + + System.Windows.Forms.Button, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + panel2 + + + 1 + + + + Right + + + + 241, 0 + + + 0, 0, 0, 0 + + + 8, 8, 8, 8 + + + 128, 271 + + + + 1 + + + panel2 + + + System.Windows.Forms.Panel, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + $this + + + 1 + + + Top, Left, Right + + + NoControl + + + 14, 15 + + + 6, 6, 6, 6 + + + 99, 26 + + + 0 + + + OK + + + 17, 17 + + + Accept the current behaviour configuration changes. + + + okButton + + + System.Windows.Forms.Button, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + panel2 + + + 0 + + + Top, Left, Right + + + NoControl + + + 14, 53 + + + 6, 6, 6, 6 + + + 99, 26 + + + 1 + + + Cancel + + + Close the behaviour configuration without saving changes. + + + cancelButton + + + System.Windows.Forms.Button, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + panel2 + + + 1 + + + False + + + Top, Bottom, Left, Right + + + True + + + NoControl + + + 6, 218 + + + 6, 0, 6, 0 + + + 101, 13 + + + 7 + + + End Transition Time + + + MiddleLeft + + + The amount of time (in milliseconds) that the end transition will take. This time extends the original image lifetime. + + + endTransitionTimeLabel + + + System.Windows.Forms.Label, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + configurationGroupBox + + + 0 + + + Top, Left, Right + + + 119, 216 + + + 6, 6, 6, 6 + + + 92, 20 + + + 8 + + + endTransitionTimeNumericUpDownWheel + + + Natsnudasoft.EgamiFlowScreensaver.NumericUpDownWheel, Natsnudasoft.EgamiFlowScreensaver, Version=0.1.0.0, Culture=neutral, PublicKeyToken=null + + + configurationGroupBox + + + 1 + + + Top, Left, Right + + + NoControl + + + 0, 135 + + + 0, 6, 0, 6 + + + 217, 2 + + + 4 + + + horizontalSplitterLabel + + + System.Windows.Forms.Label, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + configurationGroupBox + + + 2 + + + Top + + + NoControl + + + 22, 178 + + + 6, 6, 6, 6 + + + 173, 26 + + + 6 + + + Choose End Transition Colour + + + Select the colour value that the end transition will finish at (starting from the end colour value). + + + chooseEndTransitionColorButton + + + System.Windows.Forms.Button, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + configurationGroupBox + + + 3 + + + Top + + + NoControl + + + 45, 149 + + + 6, 6, 6, 6 + + + 136, 17 + + + 5 + + + End Transition Enabled + + + Whether or not the end transition will play when emitted images reach the end of their lifetime. + + + endTransitionEnabledCheckBox + + + System.Windows.Forms.CheckBox, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + configurationGroupBox + + + 4 + + + False + + + True + + + NoControl + + + 6, 105 + + + 6, 0, 6, 0 + + + 79, 13 + + + 2 + + + Transition Time + + + MiddleLeft + + + The amount of time (in milliseconds) that this transition will take. + + + transitionTimeLabel + + + System.Windows.Forms.Label, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + configurationGroupBox + + + 5 + + + Top + + + NoControl + + + 45, 65 + + + 6, 6, 6, 6 + + + 126, 26 + + + 1 + + + Choose End Colour + + + Select the end colour of this transition. + + + chooseEndColorButton + + + System.Windows.Forms.Button, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + configurationGroupBox + + + 6 + + + Top + + + NoControl + + + 45, 27 + + + 6, 6, 6, 6 + + + 126, 26 + + + 0 + + + Choose Start Colour + + + Select the start colour of this transition. + + + chooseStartColorButton + + + System.Windows.Forms.Button, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + configurationGroupBox + + + 7 + + + Top, Left, Right + + + 97, 103 + + + 6, 6, 6, 6 + + + 114, 20 + + + 3 + + + transitionTimeNumericUpDownWheel + + + Natsnudasoft.EgamiFlowScreensaver.NumericUpDownWheel, Natsnudasoft.EgamiFlowScreensaver, Version=0.1.0.0, Culture=neutral, PublicKeyToken=null + + + configurationGroupBox + + + 8 + + + 12, 12 + + + 4, 4, 4, 4 + + + 0, 8, 0, 0 + + + 217, 246 + + + 0 + + + Configuration + + + configurationGroupBox + + + System.Windows.Forms.GroupBox, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + panel1 + + + 0 + + + False + + + Fill + + + 0, 0 + + + 0, 0, 0, 0 + + + 8, 8, 8, 8 + + + 241, 271 + + + 5 + + + panel1 + + + System.Windows.Forms.Panel, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + $this + + + 0 + + + True + + + 6, 13 + + + 369, 271 + + + CenterParent + + + Colour Change Behaviour Configuration + + + configurationToolTip + + + System.Windows.Forms.ToolTip, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + ColorChangeBehaviorForm + + + System.Windows.Forms.Form, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/src/EgamiFlowScreensaver/Config/ColorChangeBehaviorFormViewModel.cs b/src/EgamiFlowScreensaver/Config/ColorChangeBehaviorFormViewModel.cs new file mode 100644 index 0000000..af938d0 --- /dev/null +++ b/src/EgamiFlowScreensaver/Config/ColorChangeBehaviorFormViewModel.cs @@ -0,0 +1,233 @@ +// +// Copyright (c) Adrian John Dunstan. All rights reserved. +// +// Licensed 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. +// + +namespace Natsnudasoft.EgamiFlowScreensaver.Config +{ + using System; + using System.Drawing; + using System.Windows.Forms; + using Natsnudasoft.EgamiFlowScreensaver.Properties; + using Natsnudasoft.NatsnudaLibrary; + + /// + /// Provides a class for managing the state of the backing values for the current state of + /// configuration for a colour change behaviour. + /// + /// + public sealed class ColorChangeBehaviorFormViewModel : ConfigurationBehaviorFormViewModel + { + private readonly ILifetimeDetails lifetimeDetails; + private double transitionTime; + private bool endTransitionEnabled; + private Color endTransitionColor; + private double endTransitionTime; + + /// + /// Initializes a new instance of the class. + /// + /// The current lifetime settings of any images emitted. + /// + /// is + /// . + public ColorChangeBehaviorFormViewModel(ILifetimeDetails lifetimeDetails) + { + ParameterValidation.IsNotNull(lifetimeDetails, nameof(lifetimeDetails)); + + this.lifetimeDetails = lifetimeDetails; + } + + /// + /// Gets the colour that the behaviour should start from. + /// + public Color StartColor { get; private set; } + + /// + /// Gets the colour that the behaviour should finish at. + /// + public Color EndColor { get; private set; } + + /// + /// Gets or sets the time that the behaviour transition should take (in milliseconds). + /// + public double TransitionTime + { + get => this.transitionTime; + set => this.Set(ref this.transitionTime, value); + } + + /// + /// Gets a value indicating whether or not images will be emitted infinitely. + /// + public bool IsInfiniteImageEmitMode + { + get => this.lifetimeDetails.IsInfiniteImageEmitMode; + } + + /// + /// Gets or sets a value indicating whether or not the ending transition will be enabled for + /// the image item this behaviour is attached to. + /// + /// if the ending transition will be enabled for the image + /// item this behaviour is attached to; otherwise . + public bool EndTransitionEnabled + { + get => this.endTransitionEnabled; + set => this.Set(ref this.endTransitionEnabled, value && this.IsInfiniteImageEmitMode); + } + + /// + /// Gets or sets the colour that the behaviour will finish at when the image item it is + /// attached to is being destroyed. + /// + public Color EndTransitionColor + { + get => this.endTransitionColor; + set => this.Set(ref this.endTransitionColor, value); + } + + /// + /// Gets or sets the time that the behaviour will take to transition when the image item + /// it is attached to is being destroyed (in milliseconds). + /// + public double EndTransitionTime + { + get => this.endTransitionTime; + set => this.Set(ref this.endTransitionTime, value); + } + + /// + /// Displays a dialog allowing the start colour to be selected. + /// + /// The window that will own any dialogs that will be displayed. + public void ChooseStartColor(IWin32Window owner) + { + if (ChooseColor(owner, this.StartColor, out var newColor)) + { + this.StartColor = newColor; + } + } + + /// + /// Displays a dialog allowing the end colour to be selected. + /// + /// The window that will own any dialogs that will be displayed. + public void ChooseEndColor(IWin32Window owner) + { + if (ChooseColor(owner, this.EndColor, out var newColor)) + { + this.EndColor = newColor; + } + } + + /// + /// Displays a dialog allowing the end transition colour to be selected. + /// + /// The window that will own any dialogs that will be displayed. + public void ChooseEndTransitionColor(IWin32Window owner) + { + if (ChooseColor(owner, this.EndTransitionColor, out var newColor)) + { + this.EndTransitionColor = newColor; + } + } + + /// + /// is + /// . + /// The specified behaviour is not a valid + /// . + public override void UpdateFromBehavior(ConfigurationBehavior behavior) + { + ParameterValidation.IsNotNull(behavior, nameof(behavior)); + + if (behavior is ColorChangeConfigurationBehavior colorChangeConfigurationBehavior) + { + this.StartColor = colorChangeConfigurationBehavior.StartColor; + this.EndColor = colorChangeConfigurationBehavior.EndColor; + this.TransitionTime = + colorChangeConfigurationBehavior.TransitionTime.TotalMilliseconds; + this.EndTransitionEnabled = colorChangeConfigurationBehavior.EndTransitionEnabled; + this.EndTransitionColor = colorChangeConfigurationBehavior.EndTransitionColor; + this.EndTransitionTime = + colorChangeConfigurationBehavior.EndTransitionTime.TotalMilliseconds; + } + else + { + throw new InvalidOperationException(nameof(behavior) + " was an unexpected type."); + } + } + + /// + public override ConfigurationBehavior CreateBehavior() + { + return new ColorChangeConfigurationBehavior + { + StartColor = this.StartColor, + EndColor = this.EndColor, + TransitionTime = + new TimeSpan((long)(this.TransitionTime * TimeSpan.TicksPerMillisecond)), + EndTransitionEnabled = this.EndTransitionEnabled, + EndTransitionColor = this.EndTransitionColor, + EndTransitionTime = + new TimeSpan((long)(this.EndTransitionTime * TimeSpan.TicksPerMillisecond)) + }; + } + + /// + public override bool Validate(IWin32Window owner) + { + var validated = true; + if (this.IsInfiniteImageEmitMode && + this.TransitionTime > this.lifetimeDetails.ImageEmitLifetime) + { + if (MessageBox.Show( + owner, + Resources.TransitionTimeLessThanLifetimeText, + Resources.TransitionTimeLessThanLifetimeCaption, + MessageBoxButtons.YesNo, + MessageBoxIcon.Question, + MessageBoxDefaultButton.Button2) == DialogResult.No) + { + validated = false; + } + } + + return validated; + } + + private static bool ChooseColor( + IWin32Window owner, + Color existingColor, + out Color newColor) + { + var result = false; + newColor = existingColor; + using (var colorDialog = new ColorDialog()) + { + colorDialog.AnyColor = true; + colorDialog.FullOpen = true; + colorDialog.Color = existingColor; + if (colorDialog.ShowDialog(owner) == DialogResult.OK) + { + newColor = colorDialog.Color; + result = true; + } + } + + return result; + } + } +} \ No newline at end of file diff --git a/src/EgamiFlowScreensaver/Config/ColorChangeConfigurationBehavior.cs b/src/EgamiFlowScreensaver/Config/ColorChangeConfigurationBehavior.cs new file mode 100644 index 0000000..a97f66a --- /dev/null +++ b/src/EgamiFlowScreensaver/Config/ColorChangeConfigurationBehavior.cs @@ -0,0 +1,121 @@ +// +// Copyright (c) Adrian John Dunstan. All rights reserved. +// +// Licensed 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. +// + +namespace Natsnudasoft.EgamiFlowScreensaver.Config +{ + using System; + using System.ComponentModel; + using System.Drawing; + using Natsnudasoft.EgamiFlowScreensaver.Properties; + using ProtoBuf; + + /// + /// Provides a class for managing the current state of configuration for a colour change + /// behaviour. + /// + /// + [ProtoContract] + public sealed class ColorChangeConfigurationBehavior : ConfigurationBehavior + { + private const long DefaultTransitionTime = 5000 * TimeSpan.TicksPerMillisecond; + private const long DefaultEndTransitionTime = 1000 * TimeSpan.TicksPerMillisecond; + + /// + public override ConfigurationBehaviorType ConfigurationBehaviorType + { + get => ConfigurationBehaviorType.ColorChange; + } + + /// + public override string Description + { + get => Resources.ColorChangeBehaviorDescription; + } + + /// + /// Gets or sets the colour that the behaviour should start from. + /// + public Color StartColor { get; set; } = Color.White; + + /// + /// Gets or sets the colour that the behaviour should finish at. + /// + public Color EndColor { get; set; } = Color.White; + + /// + /// Gets or sets the time that the behaviour transition should take. + /// + public TimeSpan TransitionTime { get; set; } = new TimeSpan(DefaultTransitionTime); + + /// + /// Gets or sets a value indicating whether or not the ending transition will be enabled for + /// the image item this behaviour is attached to. + /// + /// if the ending transition will be enabled for the image + /// item this behaviour is attached to; otherwise . + [ProtoMember(4, IsRequired = false)] + public bool EndTransitionEnabled { get; set; } + + /// + /// Gets or sets the colour that the behaviour will finish at when the image item it is + /// attached to is being destroyed. + /// + public Color EndTransitionColor { get; set; } = Color.White; + + /// + /// Gets or sets the time that the behaviour will take to transition when the image item + /// it is attached to is being destroyed. + /// + public TimeSpan EndTransitionTime { get; set; } = new TimeSpan(DefaultEndTransitionTime); + + [ProtoMember(1, DataFormat = DataFormat.FixedSize)] + private int StartColorSerialized + { + get => this.StartColor.ToArgb(); + set => this.StartColor = Color.FromArgb(value); + } + + [ProtoMember(2, DataFormat = DataFormat.FixedSize)] + private int EndColorSerialized + { + get => this.EndColor.ToArgb(); + set => this.EndColor = Color.FromArgb(value); + } + + [ProtoMember(3)] + [DefaultValue(DefaultTransitionTime)] + private long TransitionTimeSerialized + { + get => this.TransitionTime.Ticks; + set => this.TransitionTime = new TimeSpan(value); + } + + [ProtoMember(5, DataFormat = DataFormat.FixedSize, IsRequired = false)] + private int EndTransitionColorSerialized + { + get => this.EndTransitionColor.ToArgb(); + set => this.EndTransitionColor = Color.FromArgb(value); + } + + [ProtoMember(6, IsRequired = false)] + [DefaultValue(DefaultEndTransitionTime)] + private long EndTransitionTimeSerialized + { + get => this.EndTransitionTime.Ticks; + set => this.EndTransitionTime = new TimeSpan(value); + } + } +} \ No newline at end of file diff --git a/src/EgamiFlowScreensaver/Config/ConfigurationBehavior.cs b/src/EgamiFlowScreensaver/Config/ConfigurationBehavior.cs new file mode 100644 index 0000000..bb82c58 --- /dev/null +++ b/src/EgamiFlowScreensaver/Config/ConfigurationBehavior.cs @@ -0,0 +1,49 @@ +// +// Copyright (c) Adrian John Dunstan. All rights reserved. +// +// Licensed 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. +// + +namespace Natsnudasoft.EgamiFlowScreensaver.Config +{ + using ProtoBuf; + + /// + /// Provides an abstract base class for managing the state of the configuration for a behaviour. + /// + [ProtoContract] + [ProtoInclude(101, typeof(ColorChangeConfigurationBehavior))] + [ProtoInclude(102, typeof(ScaleChangeConfigurationBehavior))] + [ProtoInclude(103, typeof(AlphaChangeConfigurationBehavior))] + [ProtoInclude(104, typeof(RotationChangeConfigurationBehavior))] + public abstract class ConfigurationBehavior + { + /// + /// Gets the type of behaviour that this configuration represents. + /// + public abstract ConfigurationBehaviorType ConfigurationBehaviorType { get; } + + /// + /// Gets or sets a value indicating whether this is + /// enabled. + /// + /// if enabled; otherwise, . + [ProtoMember(1)] + public bool Enabled { get; set; } + + /// + /// Gets the description of the behaviour that this configuration represents. + /// + public abstract string Description { get; } + } +} \ No newline at end of file diff --git a/src/EgamiFlowScreensaver/Config/ConfigurationBehaviorFormViewModel.cs b/src/EgamiFlowScreensaver/Config/ConfigurationBehaviorFormViewModel.cs new file mode 100644 index 0000000..9e74b87 --- /dev/null +++ b/src/EgamiFlowScreensaver/Config/ConfigurationBehaviorFormViewModel.cs @@ -0,0 +1,52 @@ +// +// Copyright (c) Adrian John Dunstan. All rights reserved. +// +// Licensed 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. +// + +namespace Natsnudasoft.EgamiFlowScreensaver.Config +{ + using System.Windows.Forms; + + /// + /// Provides an abstract base class encapsulating a view model used for configuring various + /// behaviours that can be attached to image items. + /// + public abstract class ConfigurationBehaviorFormViewModel : ObservableBase + { + /// + /// Updates the current state of this view model that represents a behaviour based on the + /// specified configuration behaviour. + /// + /// A that will be used to update + /// this view model. + public abstract void UpdateFromBehavior(ConfigurationBehavior behavior); + + /// + /// Creates a configuration behaviour object that encapsulates the current state of + /// configuration for the behaviour this view model represents. + /// + /// A encapsulating the current configuration + /// state of the behaviour. + public abstract ConfigurationBehavior CreateBehavior(); + + /// + /// Validates that the relevant properties of this view model contain valid values to commit + /// to the underlying storage. + /// + /// The window that will own any dialogs that will be displayed. + /// if the view model is valid; otherwise + /// . + public abstract bool Validate(IWin32Window owner); + } +} \ No newline at end of file diff --git a/src/EgamiFlowScreensaver/Config/ConfigurationBehaviorType.cs b/src/EgamiFlowScreensaver/Config/ConfigurationBehaviorType.cs new file mode 100644 index 0000000..88090a1 --- /dev/null +++ b/src/EgamiFlowScreensaver/Config/ConfigurationBehaviorType.cs @@ -0,0 +1,50 @@ +// +// Copyright (c) Adrian John Dunstan. All rights reserved. +// +// Licensed 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. +// + +namespace Natsnudasoft.EgamiFlowScreensaver.Config +{ + using Natsnudasoft.EgamiFlowScreensaver.Properties; + + /// + /// Represents values of available behaviours that have associated configurations. + /// + public enum ConfigurationBehaviorType + { + /// + /// Represents a configuration for a colour change behaviour. + /// + [EnumResourceDisplayName(typeof(Resources), "ImageItemBehaviorTypeColorChange")] + ColorChange, + + /// + /// Represents a configuration for a scale change behaviour. + /// + [EnumResourceDisplayName(typeof(Resources), "ImageItemBehaviorTypeScaleChange")] + ScaleChange, + + /// + /// Represents a configuration for an alpha change behaviour. + /// + [EnumResourceDisplayName(typeof(Resources), "ImageItemBehaviorTypeAlphaChange")] + AlphaChange, + + /// + /// Represents a configuration for a rotation change behaviour. + /// + [EnumResourceDisplayName(typeof(Resources), "ImageItemBehaviorTypeRotationChange")] + RotationChange + } +} \ No newline at end of file diff --git a/src/EgamiFlowScreensaver/Config/ConfigurationForm.Designer.cs b/src/EgamiFlowScreensaver/Config/ConfigurationForm.Designer.cs index 0a4f6c2..6ffca05 100644 --- a/src/EgamiFlowScreensaver/Config/ConfigurationForm.Designer.cs +++ b/src/EgamiFlowScreensaver/Config/ConfigurationForm.Designer.cs @@ -23,11 +23,16 @@ private void InitializeComponent() System.Windows.Forms.Panel panel1; System.Windows.Forms.GroupBox imagesGroupBox; System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(ConfigurationForm)); - System.Windows.Forms.Label label3; - System.Windows.Forms.Label label1; + System.Windows.Forms.Label imageEmitLocationLabel; + System.Windows.Forms.Label maxImageEmitCountLabel; System.Windows.Forms.Label imageEmitRateLabel; - System.Windows.Forms.Label label2; + System.Windows.Forms.Label imagePositionLabel; System.Windows.Forms.Panel panel2; + this.imageEmitLifetimeNumericUpDownWheel = new Natsnudasoft.EgamiFlowScreensaver.NumericUpDownWheel(); + this.imageEmitLifetimeLabel = new System.Windows.Forms.Label(); + this.infiniteEmitModeCheckBox = new System.Windows.Forms.CheckBox(); + this.manageEmitBehaviorsButton = new System.Windows.Forms.Button(); + this.chooseCustomEmitLocationButton = new System.Windows.Forms.Button(); this.imageEmitLocationComboBox = new System.Windows.Forms.ComboBox(); this.maxImageEmitCountNumericUpDown = new Natsnudasoft.EgamiFlowScreensaver.NumericUpDownWheel(); this.imageEmitRateNumericUpDown = new Natsnudasoft.EgamiFlowScreensaver.NumericUpDownWheel(); @@ -44,17 +49,17 @@ private void InitializeComponent() this.imageRadioButton = new System.Windows.Forms.RadioButton(); this.okButton = new System.Windows.Forms.Button(); this.cancelButton = new System.Windows.Forms.Button(); - this.desktopRadioButtn = new System.Windows.Forms.RadioButton(); this.configurationToolTip = new System.Windows.Forms.ToolTip(this.components); panel1 = new System.Windows.Forms.Panel(); imagesGroupBox = new System.Windows.Forms.GroupBox(); - label3 = new System.Windows.Forms.Label(); - label1 = new System.Windows.Forms.Label(); + imageEmitLocationLabel = new System.Windows.Forms.Label(); + maxImageEmitCountLabel = new System.Windows.Forms.Label(); imageEmitRateLabel = new System.Windows.Forms.Label(); - label2 = new System.Windows.Forms.Label(); + imagePositionLabel = new System.Windows.Forms.Label(); panel2 = new System.Windows.Forms.Panel(); panel1.SuspendLayout(); imagesGroupBox.SuspendLayout(); + ((System.ComponentModel.ISupportInitialize)(this.imageEmitLifetimeNumericUpDownWheel)).BeginInit(); ((System.ComponentModel.ISupportInitialize)(this.maxImageEmitCountNumericUpDown)).BeginInit(); ((System.ComponentModel.ISupportInitialize)(this.imageEmitRateNumericUpDown)).BeginInit(); ((System.ComponentModel.ISupportInitialize)(this.imagePreviewPictureBox)).BeginInit(); @@ -72,9 +77,14 @@ private void InitializeComponent() // imagesGroupBox // resources.ApplyResources(imagesGroupBox, "imagesGroupBox"); - imagesGroupBox.Controls.Add(label3); + imagesGroupBox.Controls.Add(this.imageEmitLifetimeNumericUpDownWheel); + imagesGroupBox.Controls.Add(this.imageEmitLifetimeLabel); + imagesGroupBox.Controls.Add(this.infiniteEmitModeCheckBox); + imagesGroupBox.Controls.Add(this.manageEmitBehaviorsButton); + imagesGroupBox.Controls.Add(this.chooseCustomEmitLocationButton); + imagesGroupBox.Controls.Add(imageEmitLocationLabel); imagesGroupBox.Controls.Add(this.imageEmitLocationComboBox); - imagesGroupBox.Controls.Add(label1); + imagesGroupBox.Controls.Add(maxImageEmitCountLabel); imagesGroupBox.Controls.Add(this.maxImageEmitCountNumericUpDown); imagesGroupBox.Controls.Add(imageEmitRateLabel); imagesGroupBox.Controls.Add(this.imageEmitRateNumericUpDown); @@ -85,24 +95,67 @@ private void InitializeComponent() imagesGroupBox.Name = "imagesGroupBox"; imagesGroupBox.TabStop = false; // - // label3 + // imageEmitLifetimeNumericUpDownWheel // - resources.ApplyResources(label3, "label3"); - label3.Name = "label3"; - this.configurationToolTip.SetToolTip(label3, resources.GetString("label3.ToolTip")); + this.imageEmitLifetimeNumericUpDownWheel.DecimalPlaces = 2; + this.imageEmitLifetimeNumericUpDownWheel.Increment = new decimal(new int[] { + 100, + 0, + 0, + 0}); + resources.ApplyResources(this.imageEmitLifetimeNumericUpDownWheel, "imageEmitLifetimeNumericUpDownWheel"); + this.imageEmitLifetimeNumericUpDownWheel.Maximum = new decimal(new int[] { + 604800000, + 0, + 0, + 0}); + this.imageEmitLifetimeNumericUpDownWheel.Name = "imageEmitLifetimeNumericUpDownWheel"; + // + // imageEmitLifetimeLabel + // + resources.ApplyResources(this.imageEmitLifetimeLabel, "imageEmitLifetimeLabel"); + this.imageEmitLifetimeLabel.Name = "imageEmitLifetimeLabel"; + this.configurationToolTip.SetToolTip(this.imageEmitLifetimeLabel, resources.GetString("imageEmitLifetimeLabel.ToolTip")); + // + // infiniteEmitModeCheckBox + // + resources.ApplyResources(this.infiniteEmitModeCheckBox, "infiniteEmitModeCheckBox"); + this.infiniteEmitModeCheckBox.Name = "infiniteEmitModeCheckBox"; + this.configurationToolTip.SetToolTip(this.infiniteEmitModeCheckBox, resources.GetString("infiniteEmitModeCheckBox.ToolTip")); + this.infiniteEmitModeCheckBox.UseVisualStyleBackColor = true; + // + // manageEmitBehaviorsButton + // + resources.ApplyResources(this.manageEmitBehaviorsButton, "manageEmitBehaviorsButton"); + this.manageEmitBehaviorsButton.Name = "manageEmitBehaviorsButton"; + this.configurationToolTip.SetToolTip(this.manageEmitBehaviorsButton, resources.GetString("manageEmitBehaviorsButton.ToolTip")); + this.manageEmitBehaviorsButton.UseVisualStyleBackColor = true; + // + // chooseCustomEmitLocationButton + // + resources.ApplyResources(this.chooseCustomEmitLocationButton, "chooseCustomEmitLocationButton"); + this.chooseCustomEmitLocationButton.Name = "chooseCustomEmitLocationButton"; + this.configurationToolTip.SetToolTip(this.chooseCustomEmitLocationButton, resources.GetString("chooseCustomEmitLocationButton.ToolTip")); + this.chooseCustomEmitLocationButton.UseVisualStyleBackColor = true; + // + // imageEmitLocationLabel + // + resources.ApplyResources(imageEmitLocationLabel, "imageEmitLocationLabel"); + imageEmitLocationLabel.Name = "imageEmitLocationLabel"; + this.configurationToolTip.SetToolTip(imageEmitLocationLabel, resources.GetString("imageEmitLocationLabel.ToolTip")); // // imageEmitLocationComboBox // + resources.ApplyResources(this.imageEmitLocationComboBox, "imageEmitLocationComboBox"); this.imageEmitLocationComboBox.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList; this.imageEmitLocationComboBox.FormattingEnabled = true; - resources.ApplyResources(this.imageEmitLocationComboBox, "imageEmitLocationComboBox"); this.imageEmitLocationComboBox.Name = "imageEmitLocationComboBox"; // - // label1 + // maxImageEmitCountLabel // - resources.ApplyResources(label1, "label1"); - label1.Name = "label1"; - this.configurationToolTip.SetToolTip(label1, resources.GetString("label1.ToolTip")); + resources.ApplyResources(maxImageEmitCountLabel, "maxImageEmitCountLabel"); + maxImageEmitCountLabel.Name = "maxImageEmitCountLabel"; + this.configurationToolTip.SetToolTip(maxImageEmitCountLabel, resources.GetString("maxImageEmitCountLabel.ToolTip")); // // maxImageEmitCountNumericUpDown // @@ -166,7 +219,7 @@ private void InitializeComponent() // backgroundRadioGroupBox // resources.ApplyResources(this.backgroundRadioGroupBox, "backgroundRadioGroupBox"); - this.backgroundRadioGroupBox.Controls.Add(label2); + this.backgroundRadioGroupBox.Controls.Add(imagePositionLabel); this.backgroundRadioGroupBox.Controls.Add(this.backgroundImageScaleModeComboBox); this.backgroundRadioGroupBox.Controls.Add(this.chooseImageButton); this.backgroundRadioGroupBox.Controls.Add(this.solidColorRadioButton); @@ -178,17 +231,17 @@ private void InitializeComponent() this.backgroundRadioGroupBox.SelectedRadioValue = Natsnudasoft.EgamiFlowScreensaver.BackgroundMode.SolidColor; this.backgroundRadioGroupBox.TabStop = false; // - // label2 + // imagePositionLabel // - resources.ApplyResources(label2, "label2"); - label2.Name = "label2"; - this.configurationToolTip.SetToolTip(label2, resources.GetString("label2.ToolTip")); + resources.ApplyResources(imagePositionLabel, "imagePositionLabel"); + imagePositionLabel.Name = "imagePositionLabel"; + this.configurationToolTip.SetToolTip(imagePositionLabel, resources.GetString("imagePositionLabel.ToolTip")); // // backgroundImageScaleModeComboBox // + resources.ApplyResources(this.backgroundImageScaleModeComboBox, "backgroundImageScaleModeComboBox"); this.backgroundImageScaleModeComboBox.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList; this.backgroundImageScaleModeComboBox.FormattingEnabled = true; - resources.ApplyResources(this.backgroundImageScaleModeComboBox, "backgroundImageScaleModeComboBox"); this.backgroundImageScaleModeComboBox.Name = "backgroundImageScaleModeComboBox"; // // chooseImageButton @@ -253,14 +306,6 @@ private void InitializeComponent() this.configurationToolTip.SetToolTip(this.cancelButton, resources.GetString("cancelButton.ToolTip")); this.cancelButton.UseVisualStyleBackColor = true; // - // desktopRadioButtn - // - resources.ApplyResources(this.desktopRadioButtn, "desktopRadioButtn"); - this.desktopRadioButtn.Checked = true; - this.desktopRadioButtn.Name = "desktopRadioButtn"; - this.desktopRadioButtn.TabStop = true; - this.desktopRadioButtn.UseVisualStyleBackColor = true; - // // ConfigurationForm // this.AcceptButton = this.okButton; @@ -279,6 +324,7 @@ private void InitializeComponent() panel1.ResumeLayout(false); imagesGroupBox.ResumeLayout(false); imagesGroupBox.PerformLayout(); + ((System.ComponentModel.ISupportInitialize)(this.imageEmitLifetimeNumericUpDownWheel)).EndInit(); ((System.ComponentModel.ISupportInitialize)(this.maxImageEmitCountNumericUpDown)).EndInit(); ((System.ComponentModel.ISupportInitialize)(this.imageEmitRateNumericUpDown)).EndInit(); ((System.ComponentModel.ISupportInitialize)(this.imagePreviewPictureBox)).EndInit(); @@ -302,11 +348,15 @@ private void InitializeComponent() private System.Windows.Forms.Button addImageButton; private System.Windows.Forms.PictureBox imagePreviewPictureBox; private RadioGroupBox backgroundRadioGroupBox; - private System.Windows.Forms.RadioButton desktopRadioButtn; private NumericUpDownWheel imageEmitRateNumericUpDown; private System.Windows.Forms.ToolTip configurationToolTip; private NumericUpDownWheel maxImageEmitCountNumericUpDown; private System.Windows.Forms.ComboBox backgroundImageScaleModeComboBox; private System.Windows.Forms.ComboBox imageEmitLocationComboBox; + private System.Windows.Forms.Button chooseCustomEmitLocationButton; + private System.Windows.Forms.Button manageEmitBehaviorsButton; + private System.Windows.Forms.CheckBox infiniteEmitModeCheckBox; + private NumericUpDownWheel imageEmitLifetimeNumericUpDownWheel; + private System.Windows.Forms.Label imageEmitLifetimeLabel; } } \ No newline at end of file diff --git a/src/EgamiFlowScreensaver/Config/ConfigurationForm.cs b/src/EgamiFlowScreensaver/Config/ConfigurationForm.cs index 0dce39f..47ab7a3 100644 --- a/src/EgamiFlowScreensaver/Config/ConfigurationForm.cs +++ b/src/EgamiFlowScreensaver/Config/ConfigurationForm.cs @@ -18,7 +18,6 @@ namespace Natsnudasoft.EgamiFlowScreensaver.Config { using System; using System.Linq; - using System.Reflection; using System.Windows.Forms; using Natsnudasoft.EgamiFlowScreensaver.Properties; using Natsnudasoft.NatsnudaLibrary; @@ -88,10 +87,30 @@ public ConfigurationForm(ConfigurationFormViewModel viewModel, bool isIndependen this.viewModel.RemoveSelectedImage(this); }; - this.chooseColorButton.DataBindings.Add( + this.chooseCustomEmitLocationButton.DataBindings.Add( nameof(Button.Enabled), - this.solidColorRadioButton, - nameof(RadioButton.Checked)); + this.viewModel, + nameof(ConfigurationFormViewModel.IsCustomImageEmitLocation)); + this.chooseCustomEmitLocationButton.Click += (sender, e) => + { + this.viewModel.ChooseCustomEmitLocation(this); + }; + + this.manageEmitBehaviorsButton.Click += (sender, e) => + { + this.viewModel.ApplyBehaviors(this); + }; + + var chooseColorEnabledBinding = new Binding( + nameof(Button.Enabled), + this, + nameof(this.IsChooseColorButtonEnabled)); + this.chooseColorButton.DataBindings.Add(chooseColorEnabledBinding); + this.imageRadioButton.CheckedChanged += (sender, e) => + chooseColorEnabledBinding.ReadValue(); + this.solidColorRadioButton.CheckedChanged += (sender, e) => + chooseColorEnabledBinding.ReadValue(); + this.chooseImageButton.DataBindings.Add( nameof(Button.Enabled), this.imageRadioButton, @@ -103,11 +122,14 @@ public ConfigurationForm(ConfigurationFormViewModel viewModel, bool isIndependen nameof(RadioButton.Checked)); var imageScaleModeSource = Enum.GetValues(typeof(ImageScaleMode)) .Cast() - .Select(m => new { DisplayValue = GetEnumDisplayName(m), Value = m }) + .Select(m => new DataSourceDisplayValue( + EnumHelper.GetEnumDisplayName(m), m)) .ToArray(); this.backgroundImageScaleModeComboBox.DataSource = imageScaleModeSource; - this.backgroundImageScaleModeComboBox.DisplayMember = "DisplayValue"; - this.backgroundImageScaleModeComboBox.ValueMember = "Value"; + this.backgroundImageScaleModeComboBox.DisplayMember = + nameof(DataSourceDisplayValue.DisplayValue); + this.backgroundImageScaleModeComboBox.ValueMember = + nameof(DataSourceDisplayValue.Value); this.backgroundImageScaleModeComboBox.DataBindings.Add( nameof(ComboBox.SelectedValue), this.viewModel, @@ -115,13 +137,38 @@ public ConfigurationForm(ConfigurationFormViewModel viewModel, bool isIndependen true, DataSourceUpdateMode.OnPropertyChanged); + this.imageEmitLifetimeLabel.DataBindings.Add( + nameof(Label.Enabled), + this.viewModel, + nameof(ConfigurationFormViewModel.IsInfiniteImageEmitMode)); + + this.imageEmitLifetimeNumericUpDownWheel.DataBindings.Add( + nameof(NumericUpDownWheel.Enabled), + this.viewModel, + nameof(ConfigurationFormViewModel.IsInfiniteImageEmitMode)); + this.imageEmitLifetimeNumericUpDownWheel.DataBindings.Add( + nameof(NumericUpDownWheel.Value), + this.viewModel, + nameof(ConfigurationFormViewModel.ImageEmitLifetime), + true); + + this.infiniteEmitModeCheckBox.DataBindings.Add( + nameof(CheckBox.Checked), + this.viewModel, + nameof(ConfigurationFormViewModel.IsInfiniteImageEmitMode), + false, + DataSourceUpdateMode.OnPropertyChanged); + var imageEmitLocationSource = Enum.GetValues(typeof(ImageEmitLocation)) .Cast() - .Select(m => new { DisplayValue = GetEnumDisplayName(m), Value = m }) + .Select(m => new DataSourceDisplayValue( + EnumHelper.GetEnumDisplayName(m), m)) .ToArray(); this.imageEmitLocationComboBox.DataSource = imageEmitLocationSource; - this.imageEmitLocationComboBox.DisplayMember = "DisplayValue"; - this.imageEmitLocationComboBox.ValueMember = "Value"; + this.imageEmitLocationComboBox.DisplayMember = + nameof(DataSourceDisplayValue.DisplayValue); + this.imageEmitLocationComboBox.ValueMember = + nameof(DataSourceDisplayValue.Value); this.imageEmitLocationComboBox.DataBindings.Add( nameof(ComboBox.SelectedValue), this.viewModel, @@ -160,6 +207,7 @@ public ConfigurationForm(ConfigurationFormViewModel viewModel, bool isIndependen this.okButton.Click += (sender, e) => { + this.okButton.Focus(); if (this.viewModel.Validate(this) && this.viewModel.CommitSettingsToDisk(this)) { @@ -168,6 +216,14 @@ public ConfigurationForm(ConfigurationFormViewModel viewModel, bool isIndependen }; } + /// + /// Gets a value indicating whether the choose colour button is enabled. + /// + public bool IsChooseColorButtonEnabled + { + get => this.imageRadioButton.Checked || this.solidColorRadioButton.Checked; + } + /// protected override void OnShown(EventArgs e) { @@ -214,22 +270,5 @@ protected override void Dispose(bool disposing) base.Dispose(disposing); } - - private static string GetEnumDisplayName(T enumValue) - { - var enumValueMemberInfo = typeof(T).GetMember(enumValue.ToString()).FirstOrDefault(); - var enumDisplayName = enumValueMemberInfo?.ToString(); - if (enumValueMemberInfo != null) - { - var enumResourceDisplayNameAttribute = - enumValueMemberInfo.GetCustomAttribute(); - if (enumResourceDisplayNameAttribute != null) - { - enumDisplayName = enumResourceDisplayNameAttribute.DisplayName; - } - } - - return enumDisplayName; - } } } \ No newline at end of file diff --git a/src/EgamiFlowScreensaver/Config/ConfigurationForm.resx b/src/EgamiFlowScreensaver/Config/ConfigurationForm.resx index 18b294b..c6ef012 100644 --- a/src/EgamiFlowScreensaver/Config/ConfigurationForm.resx +++ b/src/EgamiFlowScreensaver/Config/ConfigurationForm.resx @@ -127,55 +127,226 @@ Top, Left, Right - - False - + + + 119, 299 + + + 6, 6, 6, 6 + + + 92, 20 + - + + 8 + + + imageEmitLifetimeNumericUpDownWheel + + + Natsnudasoft.EgamiFlowScreensaver.NumericUpDownWheel, Natsnudasoft.EgamiFlowScreensaver, Version=0.1.0.0, Culture=neutral, PublicKeyToken=null + + + imagesGroupBox + + + 0 + + True - + NoControl - - - 9, 302 + + 9, 301 - + 6, 0, 6, 0 - - 103, 13 + + 98, 13 - + 7 - - Image Emit Location + + Image Emit Lifetime - + MiddleLeft 17, 17 - + + The time that emitted images will stay on the screen for (in milliseconds) if 'Infinite Emit Mode' is enabled. Behaviour transitions may extend this lifetime. + + + imageEmitLifetimeLabel + + + System.Windows.Forms.Label, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + imagesGroupBox + + + 1 + + + True + + + 223, 300 + + + 6, 6, 6, 6 + + + 110, 17 + + + 9 + + + Infinite Emit Mode + + + Whether or not images will be emitted infinitely. + + + infiniteEmitModeCheckBox + + + System.Windows.Forms.CheckBox, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + imagesGroupBox + + + 2 + + + Top, Right + + + NoControl + + + 220, 364 + + + 6, 6, 6, 6 + + + 150, 26 + + + 13 + + + Manage Emit Behaviours + + + Apply and configure a list of behaviours for emitted images. + + + manageEmitBehaviorsButton + + + System.Windows.Forms.Button, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + imagesGroupBox + + + 3 + + + Top, Right + + + NoControl + + + 262, 327 + + + 6, 6, 6, 6 + + + 108, 26 + + + 12 + + + Choose Location + + + Choose a location for the custom emit location. + + + chooseCustomEmitLocationButton + + + System.Windows.Forms.Button, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + imagesGroupBox + + + 4 + + + False + + + True + + + NoControl + + + 9, 334 + + + 6, 0, 6, 0 + + + 103, 13 + + + 10 + + + Image Emit Location + + + MiddleLeft + + The location on the primary screen that images will be emitted. - - label3 + + imageEmitLocationLabel - + System.Windows.Forms.Label, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - + imagesGroupBox - - 0 + + 5 + + + Top, Left, Right - 124, 299 + 124, 331 6, 6, 6, 6 @@ -184,7 +355,7 @@ 126, 21 - 8 + 11 imageEmitLocationComboBox @@ -196,49 +367,49 @@ imagesGroupBox - 1 + 6 - + False - + True - + NoControl - + 9, 269 - + 6, 0, 6, 0 - + 113, 13 - + 5 - + Max Image Emit Count - + MiddleLeft - - The maximum number of images that will be emitted. + + The maximum number of images that will be on screen at a time. - - label1 + + maxImageEmitCountLabel - + System.Windows.Forms.Label, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - + imagesGroupBox - - 2 + + 7 134, 267 @@ -262,7 +433,7 @@ imagesGroupBox - 3 + 8 False @@ -270,6 +441,9 @@ True + + NoControl + 9, 237 @@ -301,7 +475,7 @@ imagesGroupBox - 4 + 9 106, 235 @@ -325,13 +499,16 @@ imagesGroupBox - 5 + 10 Top, Right + + NoControl + - 229, 103 + 244, 103 6, 6, 6, 6 @@ -355,7 +532,7 @@ imagesGroupBox - 6 + 11 Top, Right @@ -363,8 +540,11 @@ False + + NoControl + - 229, 65 + 244, 65 6, 6, 6, 6 @@ -391,13 +571,16 @@ imagesGroupBox - 7 + 12 Top, Right + + NoControl + - 229, 27 + 244, 27 6, 6, 6, 6 @@ -424,7 +607,7 @@ imagesGroupBox - 8 + 13 Top, Left, Right @@ -442,7 +625,7 @@ 12, 6, 6, 12 - 205, 190 + 220, 190 0 @@ -457,7 +640,7 @@ imagesGroupBox - 9 + 14 12, 12 @@ -469,7 +652,7 @@ 0, 8, 0, 0 - 361, 331 + 376, 399 0 @@ -492,50 +675,56 @@ Bottom, Left, Right - + False - + + Top, Right + + True - + NoControl - - 117, 115 + + 132, 115 - + 6, 0, 6, 0 - + 76, 13 - + 5 - + Image Position - + MiddleLeft - + How the background image will be scaled and positioned. - - label2 + + imagePositionLabel - + System.Windows.Forms.Label, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - + backgroundRadioGroupBox - + 0 + + Top, Right + - 205, 112 + 220, 112 6, 0, 6, 6 @@ -564,8 +753,11 @@ False + + NoControl + - 205, 80 + 220, 80 6, 6, 6, 6 @@ -597,6 +789,9 @@ True + + NoControl + 39, 56 @@ -633,8 +828,11 @@ False + + NoControl + - 205, 51 + 220, 51 6, 6, 6, 6 @@ -666,6 +864,9 @@ True + + NoControl + 39, 27 @@ -699,6 +900,9 @@ True + + NoControl + 39, 85 @@ -730,7 +934,7 @@ 6 - 12, 351 + 12, 418 4, 4, 4, 4 @@ -739,7 +943,7 @@ 0, 8, 0, 0 - 361, 148 + 376, 148 1 @@ -772,7 +976,7 @@ 8, 8, 8, 8 - 385, 512 + 400, 579 0 @@ -795,6 +999,9 @@ Top, Left, Right + + NoControl + 14, 15 @@ -828,6 +1035,9 @@ Top, Left, Right + + NoControl + 14, 53 @@ -862,7 +1072,7 @@ Right - 385, 0 + 400, 0 0, 0, 0, 0 @@ -871,7 +1081,7 @@ 8, 8, 8, 8 - 128, 512 + 128, 579 2 @@ -888,30 +1098,6 @@ 1 - - True - - - 39, 27 - - - 6, 6, 6, 6 - - - 65, 17 - - - 2 - - - Desktop - - - desktopRadioButtn - - - System.Windows.Forms.RadioButton, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - True @@ -919,7 +1105,7 @@ 6, 13 - 513, 512 + 528, 579 CenterParent diff --git a/src/EgamiFlowScreensaver/Config/ConfigurationFormViewModel.cs b/src/EgamiFlowScreensaver/Config/ConfigurationFormViewModel.cs index f286bf9..eeaeb3e 100644 --- a/src/EgamiFlowScreensaver/Config/ConfigurationFormViewModel.cs +++ b/src/EgamiFlowScreensaver/Config/ConfigurationFormViewModel.cs @@ -35,12 +35,14 @@ namespace Natsnudasoft.EgamiFlowScreensaver.Config /// /// /// - public sealed class ConfigurationFormViewModel : ObservableBase, IDisposable + public sealed class ConfigurationFormViewModel : ObservableBase, IDisposable, ILifetimeDetails { private static readonly ILogger Logger = LogManager.GetCurrentClassLogger(); private readonly IConfigurationFileService configurationFileService; private readonly IConfigurationFilesTempCache configurationFilesTempCache; + private readonly IBehaviorConfigurationFactory behaviorConfigurationFactory; + private readonly IBehaviorConfigurationFormFactory behaviorConfigurationFormFactory; private readonly BindingList images; private int selectedImageIndex = -1; private Image selectedImagePreview; @@ -49,6 +51,10 @@ public sealed class ConfigurationFormViewModel : ObservableBase, IDisposable private float imageEmitRate; private int maxImageEmitCount; private ImageEmitLocation imageEmitLocation; + private int customImageEmitLocationX; + private int customImageEmitLocationY; + private bool isInfiniteEmitMode; + private double emitLifetime; /// /// Initializes a new instance of the class. @@ -57,11 +63,21 @@ public sealed class ConfigurationFormViewModel : ObservableBase, IDisposable /// perform operations in the configuration directory. /// The configuration files cache to use to /// cache configuration files before they are committed. - /// is - /// . + /// The behaviour configuration factory to use + /// to create instances of behaviour configurations based on a behaviour type. + /// The behaviour configuration form factory + /// to use to create instances of behaviour configuration forms based on a behaviour type. + /// + /// , + /// , + /// , or + /// is . + /// public ConfigurationFormViewModel( IConfigurationFileService configurationFileService, - IConfigurationFilesTempCache configurationFilesTempCache) + IConfigurationFilesTempCache configurationFilesTempCache, + IBehaviorConfigurationFactory behaviorConfigurationFactory, + IBehaviorConfigurationFormFactory behaviorConfigurationFormFactory) { ParameterValidation.IsNotNull( configurationFileService, @@ -69,14 +85,23 @@ public sealed class ConfigurationFormViewModel : ObservableBase, IDisposable ParameterValidation.IsNotNull( configurationFilesTempCache, nameof(configurationFilesTempCache)); + ParameterValidation.IsNotNull( + behaviorConfigurationFactory, + nameof(behaviorConfigurationFactory)); + ParameterValidation.IsNotNull( + behaviorConfigurationFormFactory, + nameof(behaviorConfigurationFormFactory)); this.configurationFileService = configurationFileService; this.configurationFilesTempCache = configurationFilesTempCache; + this.behaviorConfigurationFactory = behaviorConfigurationFactory; + this.behaviorConfigurationFormFactory = behaviorConfigurationFormFactory; this.images = new BindingList { AllowNew = false, RaiseListChangedEvents = true }; + this.Behaviors = new List(); } /// @@ -154,7 +179,40 @@ public int MaxImageEmitCount public ImageEmitLocation ImageEmitLocation { get => this.imageEmitLocation; - set => this.Set(ref this.imageEmitLocation, value); + set + { + this.Set(ref this.imageEmitLocation, value); + this.OnPropertyChanged(nameof(this.IsCustomImageEmitLocation)); + } + } + + /// + /// Gets a value indicating whether the location that images will be emitted is a custom + /// value. + /// + public bool IsCustomImageEmitLocation + { + get => this.ImageEmitLocation == ImageEmitLocation.Custom; + } + + /// + /// Gets or sets the X coordinate of the emit location if it is in + /// mode. + /// + public int CustomImageEmitLocationX + { + get => this.customImageEmitLocationX; + set => this.Set(ref this.customImageEmitLocationX, value); + } + + /// + /// Gets or sets the Y coordinate of the emit location if it is in + /// mode. + /// + public int CustomImageEmitLocationY + { + get => this.customImageEmitLocationY; + set => this.Set(ref this.customImageEmitLocationY, value); } /// @@ -179,6 +237,34 @@ public ImageScaleMode BackgroundImageScaleMode set => this.Set(ref this.backgroundImageScaleMode, value); } + /// + /// Gets a collection of configurations for behaviours that will be attached to any images + /// emitted. + /// + public IList Behaviors { get; } + + /// + /// Gets or sets a value indicating whether or not images will be emitted infinitely. + /// + /// if images should be emitted infinitely; otherwise + /// . + public bool IsInfiniteImageEmitMode + { + get => this.isInfiniteEmitMode; + set => this.Set(ref this.isInfiniteEmitMode, value); + } + + /// + /// Gets or sets the time that emitted images should live for (in milliseconds). + /// + /// This setting is only used if is + /// . + public double ImageEmitLifetime + { + get => this.emitLifetime; + set => this.Set(ref this.emitLifetime, value); + } + /// /// Displays a dialog allowing a new image to be added to the image item collection. /// @@ -312,6 +398,50 @@ public void ChooseBackgroundImage(IWin32Window owner) } } + /// + /// Displays a dialog allowing a custom emit location to be selected. + /// + /// The window that will own any dialogs that will be displayed. + public void ChooseCustomEmitLocation(IWin32Window owner) + { + var emitLocationViewModel = new CustomEmitLocationFormViewModel(); + using (var customEmitLocationDialog = new CustomEmitLocationForm(emitLocationViewModel)) + { + emitLocationViewModel.CustomImageEmitLocationX = this.CustomImageEmitLocationX; + emitLocationViewModel.CustomImageEmitLocationY = this.CustomImageEmitLocationY; + if (customEmitLocationDialog.ShowDialog(owner) == DialogResult.OK) + { + this.CustomImageEmitLocationX = emitLocationViewModel.CustomImageEmitLocationX; + this.CustomImageEmitLocationY = emitLocationViewModel.CustomImageEmitLocationY; + } + } + } + + /// + /// Displays a dialog allowing behaviours to be applied and configured for image items. + /// + /// The window that will own any dialogs that will be displayed. + public void ApplyBehaviors(IWin32Window owner) + { + var applyBehaviorsFormViewModel = new ApplyBehaviorsFormViewModel( + this.Behaviors, + this.behaviorConfigurationFactory, + this.behaviorConfigurationFormFactory, + this); + using (var applyBehaviorsDialog = new ApplyBehaviorsForm(applyBehaviorsFormViewModel)) + { + if (applyBehaviorsDialog.ShowDialog(owner) == DialogResult.OK) + { + this.Behaviors.Clear(); + applyBehaviorsFormViewModel.SynchronizeEnabledBehaviors(); + foreach (var behavior in applyBehaviorsFormViewModel.Behaviors) + { + this.Behaviors.Add(behavior); + } + } + } + } + /// /// Reads the configuration from disk and synchronises the properties with this view model. /// @@ -350,12 +480,23 @@ public bool ReadSettingsFromDisk(IWin32Window owner) this.ImageEmitRate = screensaverConfiguration.ImageEmitRate; this.MaxImageEmitCount = screensaverConfiguration.MaxImageEmitCount; this.ImageEmitLocation = screensaverConfiguration.ImageEmitLocation; + this.CustomImageEmitLocationX = screensaverConfiguration.CustomImageEmitLocationX; + this.CustomImageEmitLocationY = screensaverConfiguration.CustomImageEmitLocationY; + this.IsInfiniteImageEmitMode = screensaverConfiguration.IsInfiniteImageEmitMode; + this.ImageEmitLifetime = + screensaverConfiguration.ImageEmitLifetime.TotalMilliseconds; this.Images.Clear(); foreach (var screensaverImage in screensaverImages) { this.Images.Add(screensaverImage); } + this.Behaviors.Clear(); + foreach (var behavior in screensaverConfiguration.Behaviors) + { + this.Behaviors.Add(behavior); + } + result = true; } catch (IOException ex) @@ -399,6 +540,7 @@ public bool CommitSettingsToDisk(IWin32Window owner) case BackgroundMode.Image: backgroundImage = this.configurationFileService .CommitCachedBackgroundImage(this.BackgroundImage); + backgroundColor = this.BackgroundColor; newBackgroundImageScaleMode = this.BackgroundImageScaleMode; break; @@ -420,7 +562,12 @@ public bool CommitSettingsToDisk(IWin32Window owner) BackgroundImageScaleMode = newBackgroundImageScaleMode, ImageEmitRate = this.ImageEmitRate, MaxImageEmitCount = this.MaxImageEmitCount, - ImageEmitLocation = this.ImageEmitLocation + ImageEmitLocation = this.ImageEmitLocation, + CustomImageEmitLocationX = this.CustomImageEmitLocationX, + CustomImageEmitLocationY = this.CustomImageEmitLocationY, + IsInfiniteImageEmitMode = this.IsInfiniteImageEmitMode, + ImageEmitLifetime = + new TimeSpan((long)(this.ImageEmitLifetime * TimeSpan.TicksPerMillisecond)) }; var committedImages = this.configurationFileService .CommitCachedScreensaverImages(this.images); @@ -429,6 +576,11 @@ public bool CommitSettingsToDisk(IWin32Window owner) screensaverConfiguration.Images.Add(configurationImageItem); } + foreach (var behavior in this.Behaviors) + { + screensaverConfiguration.Behaviors.Add(behavior); + } + try { this.configurationFileService.Save(screensaverConfiguration); diff --git a/src/EgamiFlowScreensaver/Config/CustomEmitLocationForm.Designer.cs b/src/EgamiFlowScreensaver/Config/CustomEmitLocationForm.Designer.cs new file mode 100644 index 0000000..5a269ec --- /dev/null +++ b/src/EgamiFlowScreensaver/Config/CustomEmitLocationForm.Designer.cs @@ -0,0 +1,172 @@ +namespace Natsnudasoft.EgamiFlowScreensaver.Config +{ + partial class CustomEmitLocationForm + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Windows Form Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage( + "Microsoft.Reliability", + "CA2000:Dispose objects before losing scope", + Justification = "This is auto generated code.")] + private void InitializeComponent() + { + this.components = new System.ComponentModel.Container(); + System.Windows.Forms.Panel panel2; + System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(CustomEmitLocationForm)); + System.Windows.Forms.GroupBox emitLocationGroupBox; + System.Windows.Forms.Label positionYLabel; + System.Windows.Forms.Label positionXLabel; + System.Windows.Forms.Panel panel1; + this.okButton = new System.Windows.Forms.Button(); + this.cancelButton = new System.Windows.Forms.Button(); + this.positionXNumericUpDown = new Natsnudasoft.EgamiFlowScreensaver.NumericUpDownWheel(); + this.positionYNumericUpDown = new Natsnudasoft.EgamiFlowScreensaver.NumericUpDownWheel(); + this.configurationToolTip = new System.Windows.Forms.ToolTip(this.components); + panel2 = new System.Windows.Forms.Panel(); + emitLocationGroupBox = new System.Windows.Forms.GroupBox(); + positionYLabel = new System.Windows.Forms.Label(); + positionXLabel = new System.Windows.Forms.Label(); + panel1 = new System.Windows.Forms.Panel(); + panel2.SuspendLayout(); + emitLocationGroupBox.SuspendLayout(); + ((System.ComponentModel.ISupportInitialize)(this.positionXNumericUpDown)).BeginInit(); + ((System.ComponentModel.ISupportInitialize)(this.positionYNumericUpDown)).BeginInit(); + panel1.SuspendLayout(); + this.SuspendLayout(); + // + // panel2 + // + panel2.Controls.Add(this.okButton); + panel2.Controls.Add(this.cancelButton); + resources.ApplyResources(panel2, "panel2"); + panel2.Name = "panel2"; + // + // okButton + // + resources.ApplyResources(this.okButton, "okButton"); + this.okButton.Name = "okButton"; + this.configurationToolTip.SetToolTip(this.okButton, resources.GetString("okButton.ToolTip")); + this.okButton.UseVisualStyleBackColor = true; + // + // cancelButton + // + resources.ApplyResources(this.cancelButton, "cancelButton"); + this.cancelButton.DialogResult = System.Windows.Forms.DialogResult.Cancel; + this.cancelButton.Name = "cancelButton"; + this.configurationToolTip.SetToolTip(this.cancelButton, resources.GetString("cancelButton.ToolTip")); + this.cancelButton.UseVisualStyleBackColor = true; + // + // emitLocationGroupBox + // + resources.ApplyResources(emitLocationGroupBox, "emitLocationGroupBox"); + emitLocationGroupBox.Controls.Add(positionYLabel); + emitLocationGroupBox.Controls.Add(positionXLabel); + emitLocationGroupBox.Controls.Add(this.positionXNumericUpDown); + emitLocationGroupBox.Controls.Add(this.positionYNumericUpDown); + emitLocationGroupBox.Name = "emitLocationGroupBox"; + emitLocationGroupBox.TabStop = false; + // + // positionYLabel + // + resources.ApplyResources(positionYLabel, "positionYLabel"); + positionYLabel.Name = "positionYLabel"; + this.configurationToolTip.SetToolTip(positionYLabel, resources.GetString("positionYLabel.ToolTip")); + // + // positionXLabel + // + resources.ApplyResources(positionXLabel, "positionXLabel"); + positionXLabel.Name = "positionXLabel"; + this.configurationToolTip.SetToolTip(positionXLabel, resources.GetString("positionXLabel.ToolTip")); + // + // positionXNumericUpDown + // + resources.ApplyResources(this.positionXNumericUpDown, "positionXNumericUpDown"); + this.positionXNumericUpDown.Maximum = new decimal(new int[] { + 32767, + 0, + 0, + 0}); + this.positionXNumericUpDown.Minimum = new decimal(new int[] { + 32767, + 0, + 0, + -2147483648}); + this.positionXNumericUpDown.Name = "positionXNumericUpDown"; + // + // positionYNumericUpDown + // + resources.ApplyResources(this.positionYNumericUpDown, "positionYNumericUpDown"); + this.positionYNumericUpDown.Maximum = new decimal(new int[] { + 32767, + 0, + 0, + 0}); + this.positionYNumericUpDown.Minimum = new decimal(new int[] { + 32767, + 0, + 0, + -2147483648}); + this.positionYNumericUpDown.Name = "positionYNumericUpDown"; + // + // panel1 + // + panel1.Controls.Add(emitLocationGroupBox); + resources.ApplyResources(panel1, "panel1"); + panel1.Name = "panel1"; + // + // CustomEmitLocationForm + // + this.AcceptButton = this.okButton; + resources.ApplyResources(this, "$this"); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.CancelButton = this.cancelButton; + this.Controls.Add(panel1); + this.Controls.Add(panel2); + this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedToolWindow; + this.MaximizeBox = false; + this.MinimizeBox = false; + this.Name = "CustomEmitLocationForm"; + this.ShowIcon = false; + this.ShowInTaskbar = false; + this.SizeGripStyle = System.Windows.Forms.SizeGripStyle.Hide; + panel2.ResumeLayout(false); + emitLocationGroupBox.ResumeLayout(false); + emitLocationGroupBox.PerformLayout(); + ((System.ComponentModel.ISupportInitialize)(this.positionXNumericUpDown)).EndInit(); + ((System.ComponentModel.ISupportInitialize)(this.positionYNumericUpDown)).EndInit(); + panel1.ResumeLayout(false); + this.ResumeLayout(false); + + } + + #endregion + + private System.Windows.Forms.ToolTip configurationToolTip; + private System.Windows.Forms.Button okButton; + private System.Windows.Forms.Button cancelButton; + private NumericUpDownWheel positionYNumericUpDown; + private NumericUpDownWheel positionXNumericUpDown; + } +} \ No newline at end of file diff --git a/src/EgamiFlowScreensaver/Config/CustomEmitLocationForm.cs b/src/EgamiFlowScreensaver/Config/CustomEmitLocationForm.cs new file mode 100644 index 0000000..c092db2 --- /dev/null +++ b/src/EgamiFlowScreensaver/Config/CustomEmitLocationForm.cs @@ -0,0 +1,63 @@ +// +// Copyright (c) Adrian John Dunstan. All rights reserved. +// +// Licensed 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. +// + +namespace Natsnudasoft.EgamiFlowScreensaver.Config +{ + using System; + using System.Windows.Forms; + using Natsnudasoft.NatsnudaLibrary; + + /// + /// A configuration form allowing the user to specify a custom location to emit images from. + /// + /// + public partial class CustomEmitLocationForm : Form + { + private readonly CustomEmitLocationFormViewModel viewModel; + + /// + /// Initializes a new instance of the class. + /// + /// The view model to bind to. + /// is + /// . + public CustomEmitLocationForm(CustomEmitLocationFormViewModel viewModel) + { + ParameterValidation.IsNotNull(viewModel, nameof(viewModel)); + + this.viewModel = viewModel; + this.InitializeComponent(); + + this.positionXNumericUpDown.DataBindings.Add( + nameof(NumericUpDown.Value), + this.viewModel, + nameof(CustomEmitLocationFormViewModel.CustomImageEmitLocationX), + true); + + this.positionYNumericUpDown.DataBindings.Add( + nameof(NumericUpDown.Value), + this.viewModel, + nameof(CustomEmitLocationFormViewModel.CustomImageEmitLocationY), + true); + + this.okButton.Click += (sender, e) => + { + this.okButton.Focus(); + this.DialogResult = DialogResult.OK; + }; + } + } +} \ No newline at end of file diff --git a/src/EgamiFlowScreensaver/Config/CustomEmitLocationForm.resx b/src/EgamiFlowScreensaver/Config/CustomEmitLocationForm.resx new file mode 100644 index 0000000..b2a15a6 --- /dev/null +++ b/src/EgamiFlowScreensaver/Config/CustomEmitLocationForm.resx @@ -0,0 +1,459 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + False + + + + Top, Left, Right + + + NoControl + + + + 14, 15 + + + 6, 6, 6, 6 + + + 99, 26 + + + + 0 + + + OK + + + 17, 17 + + + Confirm the current custom emit location. + + + okButton + + + System.Windows.Forms.Button, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + panel2 + + + 0 + + + Top, Left, Right + + + NoControl + + + 14, 53 + + + 6, 6, 6, 6 + + + 99, 26 + + + 1 + + + Cancel + + + Cancel changing the custom emit location. + + + cancelButton + + + System.Windows.Forms.Button, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + panel2 + + + 1 + + + Right + + + 176, 0 + + + 0, 0, 0, 0 + + + 8, 8, 8, 8 + + + 128, 107 + + + 1 + + + panel2 + + + System.Windows.Forms.Panel, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + $this + + + 1 + + + False + + + Top, Bottom, Left, Right + + + False + + + True + + + NoControl + + + 6, 54 + + + 6, 0, 6, 0 + + + 54, 13 + + + 2 + + + Y Position + + + MiddleLeft + + + The Y coordinate of the custom emit location. + + + positionYLabel + + + System.Windows.Forms.Label, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + emitLocationGroupBox + + + 0 + + + False + + + True + + + NoControl + + + 6, 26 + + + 6, 0, 6, 0 + + + 54, 13 + + + 0 + + + X Position + + + MiddleLeft + + + The X coordinate of the custom emit location. + + + positionXLabel + + + System.Windows.Forms.Label, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + emitLocationGroupBox + + + 1 + + + 72, 24 + + + 6, 6, 6, 6 + + + 71, 20 + + + 1 + + + positionXNumericUpDown + + + Natsnudasoft.EgamiFlowScreensaver.NumericUpDownWheel, Natsnudasoft.EgamiFlowScreensaver, Version=0.1.0.0, Culture=neutral, PublicKeyToken=null + + + emitLocationGroupBox + + + 2 + + + 72, 52 + + + 6, 6, 6, 6 + + + 71, 20 + + + 3 + + + positionYNumericUpDown + + + Natsnudasoft.EgamiFlowScreensaver.NumericUpDownWheel, Natsnudasoft.EgamiFlowScreensaver, Version=0.1.0.0, Culture=neutral, PublicKeyToken=null + + + emitLocationGroupBox + + + 3 + + + 12, 12 + + + 4, 4, 4, 4 + + + 0, 8, 0, 0 + + + 152, 82 + + + 0 + + + Emit Location + + + emitLocationGroupBox + + + System.Windows.Forms.GroupBox, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + panel1 + + + 0 + + + False + + + Fill + + + 0, 0 + + + 0, 0, 0, 0 + + + 8, 8, 8, 8 + + + 176, 107 + + + 0 + + + panel1 + + + System.Windows.Forms.Panel, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + $this + + + 0 + + + True + + + 6, 13 + + + 304, 107 + + + CenterParent + + + Egami Flow Screensaver Custom Emit Location + + + configurationToolTip + + + System.Windows.Forms.ToolTip, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + CustomEmitLocationForm + + + System.Windows.Forms.Form, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/src/EgamiFlowScreensaver/Config/CustomEmitLocationFormViewModel.cs b/src/EgamiFlowScreensaver/Config/CustomEmitLocationFormViewModel.cs new file mode 100644 index 0000000..c123b89 --- /dev/null +++ b/src/EgamiFlowScreensaver/Config/CustomEmitLocationFormViewModel.cs @@ -0,0 +1,47 @@ +// +// Copyright (c) Adrian John Dunstan. All rights reserved. +// +// Licensed 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. +// + +namespace Natsnudasoft.EgamiFlowScreensaver.Config +{ + /// + /// Provides a class for managing the state of the backing values for specifying a custom image + /// emit location. + /// + /// + public sealed class CustomEmitLocationFormViewModel : ObservableBase + { + private int positionX; + private int positionY; + + /// + /// Gets or sets the X coordinate of the custom emit location. + /// + public int CustomImageEmitLocationX + { + get => this.positionX; + set => this.Set(ref this.positionX, value); + } + + /// + /// Gets or sets the Y coordinate of the custom emit location. + /// + public int CustomImageEmitLocationY + { + get => this.positionY; + set => this.Set(ref this.positionY, value); + } + } +} \ No newline at end of file diff --git a/src/EgamiFlowScreensaver/Config/IBehaviorConfigurationFactory.cs b/src/EgamiFlowScreensaver/Config/IBehaviorConfigurationFactory.cs new file mode 100644 index 0000000..6cad28c --- /dev/null +++ b/src/EgamiFlowScreensaver/Config/IBehaviorConfigurationFactory.cs @@ -0,0 +1,33 @@ +// +// Copyright (c) Adrian John Dunstan. All rights reserved. +// +// Licensed 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. +// + +namespace Natsnudasoft.EgamiFlowScreensaver.Config +{ + /// + /// Provides an interface describing operations for creating behaviour configurations based on a + /// specified . + /// + public interface IBehaviorConfigurationFactory + { + /// + /// Creates a new from the specified behaviour type. + /// + /// The type of the behaviour to create a configuration for. + /// + /// The created . + ConfigurationBehavior Create(ConfigurationBehaviorType behaviorType); + } +} \ No newline at end of file diff --git a/src/EgamiFlowScreensaver/Config/IBehaviorConfigurationFormFactory.cs b/src/EgamiFlowScreensaver/Config/IBehaviorConfigurationFormFactory.cs new file mode 100644 index 0000000..ccd8955 --- /dev/null +++ b/src/EgamiFlowScreensaver/Config/IBehaviorConfigurationFormFactory.cs @@ -0,0 +1,52 @@ +// +// Copyright (c) Adrian John Dunstan. All rights reserved. +// +// Licensed 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. +// + +namespace Natsnudasoft.EgamiFlowScreensaver.Config +{ + using System.Windows.Forms; + + /// + /// Provides an interface describing operations for creating configuration forms and their + /// associated view models based on a specified . + /// + public interface IBehaviorConfigurationFormFactory + { + /// + /// Tries to create a behaviour configuration form and its' associated view model based on + /// the specified behaviour type. + /// + /// The type of the behaviour to create a configuration form for. + /// + /// The current lifetime settings of any images emitted. + /// + /// If successful contains the created behaviour configuration + /// form. + /// If successful contains the created view model + /// associated with the created behaviour configuration form. + /// if a valid behaviour configuration form and its' + /// associated view model was created; otherwise . + [System.Diagnostics.CodeAnalysis.SuppressMessage( + "Microsoft.Design", + "CA1021:AvoidOutParameters", + MessageId = "2#", + Justification = "We allow this in a 'Try' method.")] + bool TryCreate( + ConfigurationBehaviorType behaviorType, + ILifetimeDetails lifetimeDetails, + out Form behaviorForm, + out ConfigurationBehaviorFormViewModel behaviorFormViewModel); + } +} \ No newline at end of file diff --git a/src/EgamiFlowScreensaver/Config/ILifetimeDetails.cs b/src/EgamiFlowScreensaver/Config/ILifetimeDetails.cs new file mode 100644 index 0000000..02b5688 --- /dev/null +++ b/src/EgamiFlowScreensaver/Config/ILifetimeDetails.cs @@ -0,0 +1,39 @@ +// +// Copyright (c) Adrian John Dunstan. All rights reserved. +// +// Licensed 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. +// + +namespace Natsnudasoft.EgamiFlowScreensaver.Config +{ + /// + /// Provides an interface encapsulating the state of the current lifetime settings of any images + /// that will be emitted by the screensaver. + /// + public interface ILifetimeDetails + { + /// + /// Gets a value indicating whether or not images will be emitted infinitely. + /// + /// if images should be emitted infinitely; otherwise + /// . + bool IsInfiniteImageEmitMode { get; } + + /// + /// Gets the time that emitted images should live for (in milliseconds). + /// + /// This setting is only used if is + /// . + double ImageEmitLifetime { get; } + } +} \ No newline at end of file diff --git a/src/EgamiFlowScreensaver/Config/RotationChangeBehaviorForm.Designer.cs b/src/EgamiFlowScreensaver/Config/RotationChangeBehaviorForm.Designer.cs new file mode 100644 index 0000000..8828b9c --- /dev/null +++ b/src/EgamiFlowScreensaver/Config/RotationChangeBehaviorForm.Designer.cs @@ -0,0 +1,308 @@ +namespace Natsnudasoft.EgamiFlowScreensaver.Config +{ + partial class RotationChangeBehaviorForm + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Windows Form Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage( + "Microsoft.Reliability", + "CA2000:Dispose objects before losing scope", + Justification = "This is auto generated code.")] + private void InitializeComponent() + { + this.components = new System.ComponentModel.Container(); + System.Windows.Forms.Panel panel2; + System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(RotationChangeBehaviorForm)); + System.Windows.Forms.GroupBox configurationGroupBox; + System.Windows.Forms.Label transitionTimeLabel; + System.Windows.Forms.Label endRotationLabel; + System.Windows.Forms.Label startRotationLabel; + System.Windows.Forms.Panel panel1; + this.okButton = new System.Windows.Forms.Button(); + this.cancelButton = new System.Windows.Forms.Button(); + this.endTransitionRotationNumericUpDownWheel = new Natsnudasoft.EgamiFlowScreensaver.NumericUpDownWheel(); + this.endTransitionRotationLabel = new System.Windows.Forms.Label(); + this.endTransitionTimeLabel = new System.Windows.Forms.Label(); + this.endTransitionTimeNumericUpDownWheel = new Natsnudasoft.EgamiFlowScreensaver.NumericUpDownWheel(); + this.horizontalSplitterLabel = new System.Windows.Forms.Label(); + this.endTransitionEnabledCheckBox = new System.Windows.Forms.CheckBox(); + this.transitionTimeNumericUpDownWheel = new Natsnudasoft.EgamiFlowScreensaver.NumericUpDownWheel(); + this.randomlyInvertRotationCheckBox = new System.Windows.Forms.CheckBox(); + this.endRotationNumericUpDownWheel = new Natsnudasoft.EgamiFlowScreensaver.NumericUpDownWheel(); + this.startRotationNumericUpDownWheel = new Natsnudasoft.EgamiFlowScreensaver.NumericUpDownWheel(); + this.configurationToolTip = new System.Windows.Forms.ToolTip(this.components); + panel2 = new System.Windows.Forms.Panel(); + configurationGroupBox = new System.Windows.Forms.GroupBox(); + transitionTimeLabel = new System.Windows.Forms.Label(); + endRotationLabel = new System.Windows.Forms.Label(); + startRotationLabel = new System.Windows.Forms.Label(); + panel1 = new System.Windows.Forms.Panel(); + panel2.SuspendLayout(); + configurationGroupBox.SuspendLayout(); + ((System.ComponentModel.ISupportInitialize)(this.endTransitionRotationNumericUpDownWheel)).BeginInit(); + ((System.ComponentModel.ISupportInitialize)(this.endTransitionTimeNumericUpDownWheel)).BeginInit(); + ((System.ComponentModel.ISupportInitialize)(this.transitionTimeNumericUpDownWheel)).BeginInit(); + ((System.ComponentModel.ISupportInitialize)(this.endRotationNumericUpDownWheel)).BeginInit(); + ((System.ComponentModel.ISupportInitialize)(this.startRotationNumericUpDownWheel)).BeginInit(); + panel1.SuspendLayout(); + this.SuspendLayout(); + // + // panel2 + // + panel2.Controls.Add(this.okButton); + panel2.Controls.Add(this.cancelButton); + resources.ApplyResources(panel2, "panel2"); + panel2.Name = "panel2"; + // + // okButton + // + resources.ApplyResources(this.okButton, "okButton"); + this.okButton.Name = "okButton"; + this.configurationToolTip.SetToolTip(this.okButton, resources.GetString("okButton.ToolTip")); + this.okButton.UseVisualStyleBackColor = true; + // + // cancelButton + // + resources.ApplyResources(this.cancelButton, "cancelButton"); + this.cancelButton.DialogResult = System.Windows.Forms.DialogResult.Cancel; + this.cancelButton.Name = "cancelButton"; + this.configurationToolTip.SetToolTip(this.cancelButton, resources.GetString("cancelButton.ToolTip")); + this.cancelButton.UseVisualStyleBackColor = true; + // + // configurationGroupBox + // + resources.ApplyResources(configurationGroupBox, "configurationGroupBox"); + configurationGroupBox.Controls.Add(this.endTransitionRotationNumericUpDownWheel); + configurationGroupBox.Controls.Add(this.endTransitionRotationLabel); + configurationGroupBox.Controls.Add(this.endTransitionTimeLabel); + configurationGroupBox.Controls.Add(this.endTransitionTimeNumericUpDownWheel); + configurationGroupBox.Controls.Add(this.horizontalSplitterLabel); + configurationGroupBox.Controls.Add(this.endTransitionEnabledCheckBox); + configurationGroupBox.Controls.Add(transitionTimeLabel); + configurationGroupBox.Controls.Add(this.transitionTimeNumericUpDownWheel); + configurationGroupBox.Controls.Add(this.randomlyInvertRotationCheckBox); + configurationGroupBox.Controls.Add(endRotationLabel); + configurationGroupBox.Controls.Add(this.endRotationNumericUpDownWheel); + configurationGroupBox.Controls.Add(startRotationLabel); + configurationGroupBox.Controls.Add(this.startRotationNumericUpDownWheel); + configurationGroupBox.Name = "configurationGroupBox"; + configurationGroupBox.TabStop = false; + // + // endTransitionRotationNumericUpDownWheel + // + resources.ApplyResources(this.endTransitionRotationNumericUpDownWheel, "endTransitionRotationNumericUpDownWheel"); + this.endTransitionRotationNumericUpDownWheel.DecimalPlaces = 2; + this.endTransitionRotationNumericUpDownWheel.Increment = new decimal(new int[] { + 15, + 0, + 0, + 0}); + this.endTransitionRotationNumericUpDownWheel.Maximum = new decimal(new int[] { + 360000, + 0, + 0, + 0}); + this.endTransitionRotationNumericUpDownWheel.Minimum = new decimal(new int[] { + 360000, + 0, + 0, + -2147483648}); + this.endTransitionRotationNumericUpDownWheel.Name = "endTransitionRotationNumericUpDownWheel"; + // + // endTransitionRotationLabel + // + resources.ApplyResources(this.endTransitionRotationLabel, "endTransitionRotationLabel"); + this.endTransitionRotationLabel.Name = "endTransitionRotationLabel"; + this.configurationToolTip.SetToolTip(this.endTransitionRotationLabel, resources.GetString("endTransitionRotationLabel.ToolTip")); + // + // endTransitionTimeLabel + // + resources.ApplyResources(this.endTransitionTimeLabel, "endTransitionTimeLabel"); + this.endTransitionTimeLabel.Name = "endTransitionTimeLabel"; + this.configurationToolTip.SetToolTip(this.endTransitionTimeLabel, resources.GetString("endTransitionTimeLabel.ToolTip")); + // + // endTransitionTimeNumericUpDownWheel + // + resources.ApplyResources(this.endTransitionTimeNumericUpDownWheel, "endTransitionTimeNumericUpDownWheel"); + this.endTransitionTimeNumericUpDownWheel.DecimalPlaces = 2; + this.endTransitionTimeNumericUpDownWheel.Increment = new decimal(new int[] { + 100, + 0, + 0, + 0}); + this.endTransitionTimeNumericUpDownWheel.Maximum = new decimal(new int[] { + 604800000, + 0, + 0, + 0}); + this.endTransitionTimeNumericUpDownWheel.Name = "endTransitionTimeNumericUpDownWheel"; + // + // horizontalSplitterLabel + // + resources.ApplyResources(this.horizontalSplitterLabel, "horizontalSplitterLabel"); + this.horizontalSplitterLabel.BorderStyle = System.Windows.Forms.BorderStyle.Fixed3D; + this.horizontalSplitterLabel.Name = "horizontalSplitterLabel"; + // + // endTransitionEnabledCheckBox + // + resources.ApplyResources(this.endTransitionEnabledCheckBox, "endTransitionEnabledCheckBox"); + this.endTransitionEnabledCheckBox.Name = "endTransitionEnabledCheckBox"; + this.configurationToolTip.SetToolTip(this.endTransitionEnabledCheckBox, resources.GetString("endTransitionEnabledCheckBox.ToolTip")); + this.endTransitionEnabledCheckBox.UseVisualStyleBackColor = true; + // + // transitionTimeLabel + // + resources.ApplyResources(transitionTimeLabel, "transitionTimeLabel"); + transitionTimeLabel.Name = "transitionTimeLabel"; + this.configurationToolTip.SetToolTip(transitionTimeLabel, resources.GetString("transitionTimeLabel.ToolTip")); + // + // transitionTimeNumericUpDownWheel + // + resources.ApplyResources(this.transitionTimeNumericUpDownWheel, "transitionTimeNumericUpDownWheel"); + this.transitionTimeNumericUpDownWheel.DecimalPlaces = 2; + this.transitionTimeNumericUpDownWheel.Increment = new decimal(new int[] { + 100, + 0, + 0, + 0}); + this.transitionTimeNumericUpDownWheel.Maximum = new decimal(new int[] { + 604800000, + 0, + 0, + 0}); + this.transitionTimeNumericUpDownWheel.Name = "transitionTimeNumericUpDownWheel"; + // + // randomlyInvertRotationCheckBox + // + resources.ApplyResources(this.randomlyInvertRotationCheckBox, "randomlyInvertRotationCheckBox"); + this.randomlyInvertRotationCheckBox.Name = "randomlyInvertRotationCheckBox"; + this.configurationToolTip.SetToolTip(this.randomlyInvertRotationCheckBox, resources.GetString("randomlyInvertRotationCheckBox.ToolTip")); + this.randomlyInvertRotationCheckBox.UseVisualStyleBackColor = true; + // + // endRotationLabel + // + resources.ApplyResources(endRotationLabel, "endRotationLabel"); + endRotationLabel.Name = "endRotationLabel"; + this.configurationToolTip.SetToolTip(endRotationLabel, resources.GetString("endRotationLabel.ToolTip")); + // + // endRotationNumericUpDownWheel + // + resources.ApplyResources(this.endRotationNumericUpDownWheel, "endRotationNumericUpDownWheel"); + this.endRotationNumericUpDownWheel.DecimalPlaces = 2; + this.endRotationNumericUpDownWheel.Increment = new decimal(new int[] { + 15, + 0, + 0, + 0}); + this.endRotationNumericUpDownWheel.Maximum = new decimal(new int[] { + 360000, + 0, + 0, + 0}); + this.endRotationNumericUpDownWheel.Minimum = new decimal(new int[] { + 360000, + 0, + 0, + -2147483648}); + this.endRotationNumericUpDownWheel.Name = "endRotationNumericUpDownWheel"; + // + // startRotationLabel + // + resources.ApplyResources(startRotationLabel, "startRotationLabel"); + startRotationLabel.Name = "startRotationLabel"; + this.configurationToolTip.SetToolTip(startRotationLabel, resources.GetString("startRotationLabel.ToolTip")); + // + // startRotationNumericUpDownWheel + // + resources.ApplyResources(this.startRotationNumericUpDownWheel, "startRotationNumericUpDownWheel"); + this.startRotationNumericUpDownWheel.DecimalPlaces = 2; + this.startRotationNumericUpDownWheel.Increment = new decimal(new int[] { + 15, + 0, + 0, + 0}); + this.startRotationNumericUpDownWheel.Maximum = new decimal(new int[] { + 360000, + 0, + 0, + 0}); + this.startRotationNumericUpDownWheel.Minimum = new decimal(new int[] { + 360000, + 0, + 0, + -2147483648}); + this.startRotationNumericUpDownWheel.Name = "startRotationNumericUpDownWheel"; + // + // panel1 + // + panel1.Controls.Add(configurationGroupBox); + resources.ApplyResources(panel1, "panel1"); + panel1.Name = "panel1"; + // + // RotationChangeBehaviorForm + // + this.AcceptButton = this.okButton; + resources.ApplyResources(this, "$this"); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.CancelButton = this.cancelButton; + this.Controls.Add(panel1); + this.Controls.Add(panel2); + this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedToolWindow; + this.MaximizeBox = false; + this.MinimizeBox = false; + this.Name = "RotationChangeBehaviorForm"; + this.ShowIcon = false; + this.ShowInTaskbar = false; + this.SizeGripStyle = System.Windows.Forms.SizeGripStyle.Hide; + panel2.ResumeLayout(false); + configurationGroupBox.ResumeLayout(false); + configurationGroupBox.PerformLayout(); + ((System.ComponentModel.ISupportInitialize)(this.endTransitionRotationNumericUpDownWheel)).EndInit(); + ((System.ComponentModel.ISupportInitialize)(this.endTransitionTimeNumericUpDownWheel)).EndInit(); + ((System.ComponentModel.ISupportInitialize)(this.transitionTimeNumericUpDownWheel)).EndInit(); + ((System.ComponentModel.ISupportInitialize)(this.endRotationNumericUpDownWheel)).EndInit(); + ((System.ComponentModel.ISupportInitialize)(this.startRotationNumericUpDownWheel)).EndInit(); + panel1.ResumeLayout(false); + this.ResumeLayout(false); + + } + + #endregion + + private System.Windows.Forms.Button okButton; + private System.Windows.Forms.ToolTip configurationToolTip; + private System.Windows.Forms.Button cancelButton; + private NumericUpDownWheel startRotationNumericUpDownWheel; + private NumericUpDownWheel endRotationNumericUpDownWheel; + private System.Windows.Forms.CheckBox randomlyInvertRotationCheckBox; + private NumericUpDownWheel transitionTimeNumericUpDownWheel; + private System.Windows.Forms.Label endTransitionTimeLabel; + private NumericUpDownWheel endTransitionTimeNumericUpDownWheel; + private System.Windows.Forms.Label horizontalSplitterLabel; + private System.Windows.Forms.CheckBox endTransitionEnabledCheckBox; + private NumericUpDownWheel endTransitionRotationNumericUpDownWheel; + private System.Windows.Forms.Label endTransitionRotationLabel; + } +} \ No newline at end of file diff --git a/src/EgamiFlowScreensaver/Config/RotationChangeBehaviorForm.cs b/src/EgamiFlowScreensaver/Config/RotationChangeBehaviorForm.cs new file mode 100644 index 0000000..07278c3 --- /dev/null +++ b/src/EgamiFlowScreensaver/Config/RotationChangeBehaviorForm.cs @@ -0,0 +1,119 @@ +// +// Copyright (c) Adrian John Dunstan. All rights reserved. +// +// Licensed 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. +// + +namespace Natsnudasoft.EgamiFlowScreensaver.Config +{ + using System; + using System.Windows.Forms; + using Natsnudasoft.NatsnudaLibrary; + + /// + /// A form allowing configuration of a behaviour that causes an image item to transition from + /// one rotation value to another over a specified time frame. + /// + /// + public partial class RotationChangeBehaviorForm : Form + { + private readonly RotationChangeBehaviorFormViewModel viewModel; + + /// + /// Initializes a new instance of the class. + /// + /// The view model to bind to. + /// is + /// . + public RotationChangeBehaviorForm(RotationChangeBehaviorFormViewModel viewModel) + { + ParameterValidation.IsNotNull(viewModel, nameof(viewModel)); + + this.viewModel = viewModel; + this.InitializeComponent(); + + this.startRotationNumericUpDownWheel.DataBindings.Add( + nameof(NumericUpDown.Value), + this.viewModel, + nameof(RotationChangeBehaviorFormViewModel.StartRotation), + true); + + this.endRotationNumericUpDownWheel.DataBindings.Add( + nameof(NumericUpDown.Value), + this.viewModel, + nameof(RotationChangeBehaviorFormViewModel.EndRotation), + true); + + this.randomlyInvertRotationCheckBox.DataBindings.Add( + nameof(CheckBox.Checked), + this.viewModel, + nameof(RotationChangeBehaviorFormViewModel.RandomlyInvertRotation)); + + this.transitionTimeNumericUpDownWheel.DataBindings.Add( + nameof(NumericUpDown.Value), + this.viewModel, + nameof(RotationChangeBehaviorFormViewModel.TransitionTime), + true); + + this.endTransitionEnabledCheckBox.DataBindings.Add( + nameof(CheckBox.Enabled), + this.viewModel, + nameof(RotationChangeBehaviorFormViewModel.IsInfiniteImageEmitMode)); + this.endTransitionEnabledCheckBox.DataBindings.Add( + nameof(CheckBox.Checked), + this.viewModel, + nameof(RotationChangeBehaviorFormViewModel.EndTransitionEnabled), + false, + DataSourceUpdateMode.OnPropertyChanged); + + this.endTransitionRotationLabel.DataBindings.Add( + nameof(Label.Enabled), + this.viewModel, + nameof(AlphaChangeBehaviorFormViewModel.EndTransitionEnabled)); + + this.endTransitionRotationNumericUpDownWheel.DataBindings.Add( + nameof(NumericUpDownWheel.Enabled), + this.viewModel, + nameof(RotationChangeBehaviorFormViewModel.EndTransitionEnabled)); + this.endTransitionRotationNumericUpDownWheel.DataBindings.Add( + nameof(NumericUpDownWheel.Value), + this.viewModel, + nameof(RotationChangeBehaviorFormViewModel.EndTransitionRotation), + true); + + this.endTransitionTimeLabel.DataBindings.Add( + nameof(Label.Enabled), + this.viewModel, + nameof(RotationChangeBehaviorFormViewModel.EndTransitionEnabled)); + + this.endTransitionTimeNumericUpDownWheel.DataBindings.Add( + nameof(NumericUpDownWheel.Enabled), + this.viewModel, + nameof(RotationChangeBehaviorFormViewModel.EndTransitionEnabled)); + this.endTransitionTimeNumericUpDownWheel.DataBindings.Add( + nameof(NumericUpDownWheel.Value), + this.viewModel, + nameof(RotationChangeBehaviorFormViewModel.EndTransitionTime), + true); + + this.okButton.Click += (sender, e) => + { + if (this.viewModel.Validate(this)) + { + this.okButton.Focus(); + this.DialogResult = DialogResult.OK; + } + }; + } + } +} \ No newline at end of file diff --git a/src/EgamiFlowScreensaver/Config/RotationChangeBehaviorForm.resx b/src/EgamiFlowScreensaver/Config/RotationChangeBehaviorForm.resx new file mode 100644 index 0000000..bd1bfa2 --- /dev/null +++ b/src/EgamiFlowScreensaver/Config/RotationChangeBehaviorForm.resx @@ -0,0 +1,786 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + False + + + okButton + + + System.Windows.Forms.Button, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + panel2 + + + 0 + + + cancelButton + + + System.Windows.Forms.Button, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + panel2 + + + 1 + + + + Right + + + + 238, 0 + + + 0, 0, 0, 0 + + + 8, 8, 8, 8 + + + 128, 275 + + + + 1 + + + panel2 + + + System.Windows.Forms.Panel, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + $this + + + 1 + + + Top, Left, Right + + + NoControl + + + 14, 15 + + + 6, 6, 6, 6 + + + 99, 26 + + + 0 + + + OK + + + 17, 17 + + + Accept the current behaviour configuration changes. + + + okButton + + + System.Windows.Forms.Button, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + panel2 + + + 0 + + + Top, Left, Right + + + NoControl + + + 14, 53 + + + 6, 6, 6, 6 + + + 99, 26 + + + 1 + + + Cancel + + + Close the behaviour configuration without saving changes. + + + cancelButton + + + System.Windows.Forms.Button, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + panel2 + + + 1 + + + False + + + Top, Bottom, Left, Right + + + Top, Left, Right + + + 136, 189 + + + 6, 6, 6, 6 + + + 75, 20 + + + 10 + + + endTransitionRotationNumericUpDownWheel + + + Natsnudasoft.EgamiFlowScreensaver.NumericUpDownWheel, Natsnudasoft.EgamiFlowScreensaver, Version=0.1.0.0, Culture=neutral, PublicKeyToken=null + + + configurationGroupBox + + + 0 + + + True + + + NoControl + + + 6, 191 + + + 6, 0, 6, 0 + + + 118, 13 + + + 9 + + + End Transition Rotation + + + MiddleLeft + + + The rotation value that the end transition will finish at (starting from the end rotation value). + + + endTransitionRotationLabel + + + System.Windows.Forms.Label, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + configurationGroupBox + + + 1 + + + True + + + NoControl + + + 6, 223 + + + 6, 0, 6, 0 + + + 101, 13 + + + 11 + + + End Transition Time + + + MiddleLeft + + + The amount of time (in milliseconds) that the end transition will take. This time extends the original image lifetime. + + + endTransitionTimeLabel + + + System.Windows.Forms.Label, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + configurationGroupBox + + + 2 + + + Top, Left, Right + + + 119, 221 + + + 6, 6, 6, 6 + + + 92, 20 + + + 12 + + + endTransitionTimeNumericUpDownWheel + + + Natsnudasoft.EgamiFlowScreensaver.NumericUpDownWheel, Natsnudasoft.EgamiFlowScreensaver, Version=0.1.0.0, Culture=neutral, PublicKeyToken=null + + + configurationGroupBox + + + 3 + + + Top, Left, Right + + + NoControl + + + 0, 146 + + + 0, 6, 0, 6 + + + 217, 2 + + + 7 + + + horizontalSplitterLabel + + + System.Windows.Forms.Label, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + configurationGroupBox + + + 4 + + + Top + + + NoControl + + + 45, 160 + + + 6, 6, 6, 6 + + + 136, 17 + + + 8 + + + End Transition Enabled + + + Whether or not the end transition will play when emitted images reach the end of their lifetime. + + + endTransitionEnabledCheckBox + + + System.Windows.Forms.CheckBox, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + configurationGroupBox + + + 5 + + + False + + + True + + + NoControl + + + 6, 116 + + + 6, 0, 6, 0 + + + 79, 13 + + + 5 + + + Transition Time + + + MiddleLeft + + + The amount of time (in milliseconds) that this transition will take. + + + transitionTimeLabel + + + System.Windows.Forms.Label, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + configurationGroupBox + + + 6 + + + Top, Left, Right + + + 97, 114 + + + 6, 6, 6, 6 + + + 114, 20 + + + 6 + + + transitionTimeNumericUpDownWheel + + + Natsnudasoft.EgamiFlowScreensaver.NumericUpDownWheel, Natsnudasoft.EgamiFlowScreensaver, Version=0.1.0.0, Culture=neutral, PublicKeyToken=null + + + configurationGroupBox + + + 7 + + + True + + + 9, 88 + + + 146, 17 + + + 4 + + + Randomly Invert Rotation + + + Whether or not the direction of rotation for each emitted item will be inverted randomly. + + + randomlyInvertRotationCheckBox + + + System.Windows.Forms.CheckBox, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + configurationGroupBox + + + 8 + + + False + + + True + + + NoControl + + + 6, 61 + + + 6, 0, 6, 0 + + + 69, 13 + + + 2 + + + End Rotation + + + MiddleLeft + + + The rotation value (in degrees) that this transition will end at. + + + endRotationLabel + + + System.Windows.Forms.Label, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + configurationGroupBox + + + 9 + + + Top, Left, Right + + + 87, 59 + + + 6, 6, 6, 6 + + + 124, 20 + + + 3 + + + endRotationNumericUpDownWheel + + + Natsnudasoft.EgamiFlowScreensaver.NumericUpDownWheel, Natsnudasoft.EgamiFlowScreensaver, Version=0.1.0.0, Culture=neutral, PublicKeyToken=null + + + configurationGroupBox + + + 10 + + + False + + + True + + + NoControl + + + 6, 29 + + + 6, 0, 6, 0 + + + 72, 13 + + + 0 + + + Start Rotation + + + MiddleLeft + + + The rotation value (in degrees) that this transition will start from. + + + startRotationLabel + + + System.Windows.Forms.Label, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + configurationGroupBox + + + 11 + + + Top, Left, Right + + + 90, 27 + + + 6, 6, 6, 6 + + + 121, 20 + + + 1 + + + startRotationNumericUpDownWheel + + + Natsnudasoft.EgamiFlowScreensaver.NumericUpDownWheel, Natsnudasoft.EgamiFlowScreensaver, Version=0.1.0.0, Culture=neutral, PublicKeyToken=null + + + configurationGroupBox + + + 12 + + + 12, 12 + + + 4, 4, 4, 4 + + + 0, 8, 0, 0 + + + 217, 250 + + + 0 + + + Configuration + + + configurationGroupBox + + + System.Windows.Forms.GroupBox, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + panel1 + + + 0 + + + False + + + Fill + + + 0, 0 + + + 0, 0, 0, 0 + + + 8, 8, 8, 8 + + + 238, 275 + + + 7 + + + panel1 + + + System.Windows.Forms.Panel, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + $this + + + 0 + + + True + + + 6, 13 + + + 366, 275 + + + CenterParent + + + Rotation Change Behaviour Configuration + + + configurationToolTip + + + System.Windows.Forms.ToolTip, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + RotationChangeBehaviorForm + + + System.Windows.Forms.Form, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/src/EgamiFlowScreensaver/Config/RotationChangeBehaviorFormViewModel.cs b/src/EgamiFlowScreensaver/Config/RotationChangeBehaviorFormViewModel.cs new file mode 100644 index 0000000..cc279fb --- /dev/null +++ b/src/EgamiFlowScreensaver/Config/RotationChangeBehaviorFormViewModel.cs @@ -0,0 +1,201 @@ +// +// Copyright (c) Adrian John Dunstan. All rights reserved. +// +// Licensed 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. +// + +namespace Natsnudasoft.EgamiFlowScreensaver.Config +{ + using System; + using System.Windows.Forms; + using Natsnudasoft.EgamiFlowScreensaver.Properties; + using Natsnudasoft.NatsnudaLibrary; + + /// + /// Provides a class for managing the state of the backing values for the current state of + /// configuration for a rotation change behaviour. + /// + /// + public sealed class RotationChangeBehaviorFormViewModel : ConfigurationBehaviorFormViewModel + { + private readonly ILifetimeDetails lifetimeDetails; + private double transitionTime; + private float startRotation; + private float endRotation; + private bool randomlyInvertRotation; + private bool endTransitionEnabled; + private float endTransitionRotation; + private double endTransitionTime; + + /// + /// Initializes a new instance of the + /// class. + /// + /// The current lifetime settings of any images emitted. + /// + /// is + /// . + public RotationChangeBehaviorFormViewModel(ILifetimeDetails lifetimeDetails) + { + ParameterValidation.IsNotNull(lifetimeDetails, nameof(lifetimeDetails)); + + this.lifetimeDetails = lifetimeDetails; + } + + /// + /// Gets or sets the rotation value that the behaviour should start from. + /// + public float StartRotation + { + get => this.startRotation; + set => this.Set(ref this.startRotation, value); + } + + /// + /// Gets or sets the rotation value that the behaviour should finish at. + /// + public float EndRotation + { + get => this.endRotation; + set => this.Set(ref this.endRotation, value); + } + + /// + /// Gets or sets a value indicating whether the behaviour should randomly invert the values + /// of rotation each time an image with this behaviour attached is emitted. + /// + public bool RandomlyInvertRotation + { + get => this.randomlyInvertRotation; + set => this.Set(ref this.randomlyInvertRotation, value); + } + + /// + /// Gets or sets the time that the behaviour transition should take (in milliseconds). + /// + public double TransitionTime + { + get => this.transitionTime; + set => this.Set(ref this.transitionTime, value); + } + + /// + /// Gets a value indicating whether or not images will be emitted infinitely. + /// + public bool IsInfiniteImageEmitMode + { + get => this.lifetimeDetails.IsInfiniteImageEmitMode; + } + + /// + /// Gets or sets a value indicating whether or not the ending transition will be enabled for + /// the image item this behaviour is attached to. + /// + /// if the ending transition will be enabled for the image + /// item this behaviour is attached to; otherwise . + public bool EndTransitionEnabled + { + get => this.endTransitionEnabled; + set => this.Set(ref this.endTransitionEnabled, value && this.IsInfiniteImageEmitMode); + } + + /// + /// Gets or sets the rotation value that the behaviour will finish at when the image item + /// it is attached to is being destroyed. + /// + public float EndTransitionRotation + { + get => this.endTransitionRotation; + set => this.Set(ref this.endTransitionRotation, value); + } + + /// + /// Gets or sets the time that the behaviour will take to transition when the image item + /// it is attached to is being destroyed (in milliseconds). + /// + public double EndTransitionTime + { + get => this.endTransitionTime; + set => this.Set(ref this.endTransitionTime, value); + } + + /// + /// is + /// . + /// The specified behaviour is not a valid + /// . + public override void UpdateFromBehavior(ConfigurationBehavior behavior) + { + ParameterValidation.IsNotNull(behavior, nameof(behavior)); + + if (behavior is RotationChangeConfigurationBehavior rotationChangeConfigurationBehavior) + { + this.StartRotation = rotationChangeConfigurationBehavior.StartRotation; + this.EndRotation = rotationChangeConfigurationBehavior.EndRotation; + this.RandomlyInvertRotation = + rotationChangeConfigurationBehavior.RandomlyInvertRotation; + this.TransitionTime = + rotationChangeConfigurationBehavior.TransitionTime.TotalMilliseconds; + this.EndTransitionEnabled = + rotationChangeConfigurationBehavior.EndTransitionEnabled; + this.EndTransitionRotation = + rotationChangeConfigurationBehavior.EndTransitionRotation; + this.EndTransitionTime = + rotationChangeConfigurationBehavior.EndTransitionTime.TotalMilliseconds; + } + else + { + throw new InvalidOperationException(nameof(behavior) + " was an unexpected type."); + } + } + + /// + public override ConfigurationBehavior CreateBehavior() + { + return new RotationChangeConfigurationBehavior + { + StartRotation = this.StartRotation, + EndRotation = this.EndRotation, + RandomlyInvertRotation = this.RandomlyInvertRotation, + TransitionTime = + new TimeSpan((long)(this.TransitionTime * TimeSpan.TicksPerMillisecond)), + EndTransitionEnabled = this.EndTransitionEnabled, + EndTransitionRotation = this.EndTransitionRotation, + EndTransitionTime = + new TimeSpan((long)(this.EndTransitionTime * TimeSpan.TicksPerMillisecond)) + }; + } + + /// + public override bool Validate(IWin32Window owner) + { + var validated = true; + if (this.IsInfiniteImageEmitMode && + this.TransitionTime > this.lifetimeDetails.ImageEmitLifetime) + { + if (MessageBox.Show( + owner, + Resources.TransitionTimeLessThanLifetimeText, + Resources.TransitionTimeLessThanLifetimeCaption, + MessageBoxButtons.YesNo, + MessageBoxIcon.Question, + MessageBoxDefaultButton.Button2) == DialogResult.No) + { + validated = false; + } + } + + return validated; + } + } +} \ No newline at end of file diff --git a/src/EgamiFlowScreensaver/Config/RotationChangeConfigurationBehavior.cs b/src/EgamiFlowScreensaver/Config/RotationChangeConfigurationBehavior.cs new file mode 100644 index 0000000..9f63933 --- /dev/null +++ b/src/EgamiFlowScreensaver/Config/RotationChangeConfigurationBehavior.cs @@ -0,0 +1,111 @@ +// +// Copyright (c) Adrian John Dunstan. All rights reserved. +// +// Licensed 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. +// + +namespace Natsnudasoft.EgamiFlowScreensaver.Config +{ + using System; + using System.ComponentModel; + using Natsnudasoft.EgamiFlowScreensaver.Properties; + using ProtoBuf; + + /// + /// Provides a class for managing the current state of configuration for a rotation change + /// behaviour. + /// + /// + [ProtoContract] + public class RotationChangeConfigurationBehavior : ConfigurationBehavior + { + private const long DefaultTransitionTime = 750 * TimeSpan.TicksPerMillisecond; + private const long DefaultEndTransitionTime = 500 * TimeSpan.TicksPerMillisecond; + + /// + public override ConfigurationBehaviorType ConfigurationBehaviorType + { + get => ConfigurationBehaviorType.RotationChange; + } + + /// + public override string Description + { + get => Resources.RotationChangeBehaviorDescription; + } + + /// + /// Gets or sets the rotation value that the behaviour should start from. + /// + [ProtoMember(1)] + public float StartRotation { get; set; } = 0f; + + /// + /// Gets or sets the rotation value that the behaviour should finish at. + /// + [ProtoMember(2)] + [DefaultValue(360f)] + public float EndRotation { get; set; } = 360f; + + /// + /// Gets or sets a value indicating whether the behaviour should randomly invert the values + /// of rotation each time an image with this behaviour attached is emitted. + /// + [ProtoMember(3)] + [DefaultValue(true)] + public bool RandomlyInvertRotation { get; set; } = true; + + /// + /// Gets or sets the time that the behaviour transition should take. + /// + public TimeSpan TransitionTime { get; set; } = new TimeSpan(DefaultTransitionTime); + + /// + /// Gets or sets a value indicating whether or not the ending transition will be enabled for + /// the image item this behaviour is attached to. + /// + /// if the ending transition will be enabled for the image + /// item this behaviour is attached to; otherwise . + [ProtoMember(5, IsRequired = false)] + public bool EndTransitionEnabled { get; set; } + + /// + /// Gets or sets the rotation value that the behaviour will finish at when the image item + /// it is attached to is being destroyed. + /// + [ProtoMember(6, IsRequired = false)] + public float EndTransitionRotation { get; set; } + + /// + /// Gets or sets the time that the behaviour will take to transition when the image item + /// it is attached to is being destroyed. + /// + public TimeSpan EndTransitionTime { get; set; } = new TimeSpan(DefaultEndTransitionTime); + + [ProtoMember(4)] + [DefaultValue(DefaultTransitionTime)] + private long TransitionTimeSerialized + { + get => this.TransitionTime.Ticks; + set => this.TransitionTime = new TimeSpan(value); + } + + [ProtoMember(7, IsRequired = false)] + [DefaultValue(DefaultEndTransitionTime)] + private long EndTransitionTimeSerialized + { + get => this.EndTransitionTime.Ticks; + set => this.EndTransitionTime = new TimeSpan(value); + } + } +} \ No newline at end of file diff --git a/src/EgamiFlowScreensaver/Config/ScaleChangeBehaviorForm.Designer.cs b/src/EgamiFlowScreensaver/Config/ScaleChangeBehaviorForm.Designer.cs new file mode 100644 index 0000000..ae77134 --- /dev/null +++ b/src/EgamiFlowScreensaver/Config/ScaleChangeBehaviorForm.Designer.cs @@ -0,0 +1,358 @@ +namespace Natsnudasoft.EgamiFlowScreensaver.Config +{ + partial class ScaleChangeBehaviorForm + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Windows Form Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage( + "Microsoft.Reliability", + "CA2000:Dispose objects before losing scope", + Justification = "This is auto generated code.")] + private void InitializeComponent() + { + this.components = new System.ComponentModel.Container(); + System.Windows.Forms.GroupBox configurationGroupBox; + System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(ScaleChangeBehaviorForm)); + System.Windows.Forms.Label endScaleLabel; + System.Windows.Forms.Label startScaleLabel; + System.Windows.Forms.Label transitionTimeLabel; + System.Windows.Forms.Panel panel2; + System.Windows.Forms.Panel panel1; + this.yCoordinateEndTransitionLabel = new System.Windows.Forms.Label(); + this.xCoordinateEndTransitionLabel = new System.Windows.Forms.Label(); + this.endTransitionScaleYNumericUpDownWheel = new Natsnudasoft.EgamiFlowScreensaver.NumericUpDownWheel(); + this.endTransitionScaleXNumericUpDownWheel = new Natsnudasoft.EgamiFlowScreensaver.NumericUpDownWheel(); + this.endTransitionScaleLabel = new System.Windows.Forms.Label(); + this.endTransitionTimeLabel = new System.Windows.Forms.Label(); + this.endTransitionTimeNumericUpDownWheel = new Natsnudasoft.EgamiFlowScreensaver.NumericUpDownWheel(); + this.horizontalSplitterLabel = new System.Windows.Forms.Label(); + this.endTransitionEnabledCheckBox = new System.Windows.Forms.CheckBox(); + this.endScaleYNumericUpDownWheel = new Natsnudasoft.EgamiFlowScreensaver.NumericUpDownWheel(); + this.endScaleXNumericUpDownWheel = new Natsnudasoft.EgamiFlowScreensaver.NumericUpDownWheel(); + this.yCoordinateLabel = new System.Windows.Forms.Label(); + this.xCoordinateLabel = new System.Windows.Forms.Label(); + this.startScaleYNumericUpDownWheel = new Natsnudasoft.EgamiFlowScreensaver.NumericUpDownWheel(); + this.startScaleXNumericUpDownWheel = new Natsnudasoft.EgamiFlowScreensaver.NumericUpDownWheel(); + this.transitionTimeNumericUpDownWheel = new Natsnudasoft.EgamiFlowScreensaver.NumericUpDownWheel(); + this.okButton = new System.Windows.Forms.Button(); + this.cancelButton = new System.Windows.Forms.Button(); + this.configurationToolTip = new System.Windows.Forms.ToolTip(this.components); + configurationGroupBox = new System.Windows.Forms.GroupBox(); + endScaleLabel = new System.Windows.Forms.Label(); + startScaleLabel = new System.Windows.Forms.Label(); + transitionTimeLabel = new System.Windows.Forms.Label(); + panel2 = new System.Windows.Forms.Panel(); + panel1 = new System.Windows.Forms.Panel(); + configurationGroupBox.SuspendLayout(); + ((System.ComponentModel.ISupportInitialize)(this.endTransitionScaleYNumericUpDownWheel)).BeginInit(); + ((System.ComponentModel.ISupportInitialize)(this.endTransitionScaleXNumericUpDownWheel)).BeginInit(); + ((System.ComponentModel.ISupportInitialize)(this.endTransitionTimeNumericUpDownWheel)).BeginInit(); + ((System.ComponentModel.ISupportInitialize)(this.endScaleYNumericUpDownWheel)).BeginInit(); + ((System.ComponentModel.ISupportInitialize)(this.endScaleXNumericUpDownWheel)).BeginInit(); + ((System.ComponentModel.ISupportInitialize)(this.startScaleYNumericUpDownWheel)).BeginInit(); + ((System.ComponentModel.ISupportInitialize)(this.startScaleXNumericUpDownWheel)).BeginInit(); + ((System.ComponentModel.ISupportInitialize)(this.transitionTimeNumericUpDownWheel)).BeginInit(); + panel2.SuspendLayout(); + panel1.SuspendLayout(); + this.SuspendLayout(); + // + // configurationGroupBox + // + resources.ApplyResources(configurationGroupBox, "configurationGroupBox"); + configurationGroupBox.Controls.Add(this.yCoordinateEndTransitionLabel); + configurationGroupBox.Controls.Add(this.xCoordinateEndTransitionLabel); + configurationGroupBox.Controls.Add(this.endTransitionScaleYNumericUpDownWheel); + configurationGroupBox.Controls.Add(this.endTransitionScaleXNumericUpDownWheel); + configurationGroupBox.Controls.Add(this.endTransitionScaleLabel); + configurationGroupBox.Controls.Add(this.endTransitionTimeLabel); + configurationGroupBox.Controls.Add(this.endTransitionTimeNumericUpDownWheel); + configurationGroupBox.Controls.Add(this.horizontalSplitterLabel); + configurationGroupBox.Controls.Add(this.endTransitionEnabledCheckBox); + configurationGroupBox.Controls.Add(this.endScaleYNumericUpDownWheel); + configurationGroupBox.Controls.Add(endScaleLabel); + configurationGroupBox.Controls.Add(this.endScaleXNumericUpDownWheel); + configurationGroupBox.Controls.Add(this.yCoordinateLabel); + configurationGroupBox.Controls.Add(this.xCoordinateLabel); + configurationGroupBox.Controls.Add(this.startScaleYNumericUpDownWheel); + configurationGroupBox.Controls.Add(startScaleLabel); + configurationGroupBox.Controls.Add(this.startScaleXNumericUpDownWheel); + configurationGroupBox.Controls.Add(transitionTimeLabel); + configurationGroupBox.Controls.Add(this.transitionTimeNumericUpDownWheel); + configurationGroupBox.Name = "configurationGroupBox"; + configurationGroupBox.TabStop = false; + // + // yCoordinateEndTransitionLabel + // + resources.ApplyResources(this.yCoordinateEndTransitionLabel, "yCoordinateEndTransitionLabel"); + this.yCoordinateEndTransitionLabel.Name = "yCoordinateEndTransitionLabel"; + // + // xCoordinateEndTransitionLabel + // + resources.ApplyResources(this.xCoordinateEndTransitionLabel, "xCoordinateEndTransitionLabel"); + this.xCoordinateEndTransitionLabel.Name = "xCoordinateEndTransitionLabel"; + // + // endTransitionScaleYNumericUpDownWheel + // + this.endTransitionScaleYNumericUpDownWheel.DecimalPlaces = 5; + this.endTransitionScaleYNumericUpDownWheel.Increment = new decimal(new int[] { + 1, + 0, + 0, + 131072}); + resources.ApplyResources(this.endTransitionScaleYNumericUpDownWheel, "endTransitionScaleYNumericUpDownWheel"); + this.endTransitionScaleYNumericUpDownWheel.Name = "endTransitionScaleYNumericUpDownWheel"; + // + // endTransitionScaleXNumericUpDownWheel + // + this.endTransitionScaleXNumericUpDownWheel.DecimalPlaces = 5; + this.endTransitionScaleXNumericUpDownWheel.Increment = new decimal(new int[] { + 1, + 0, + 0, + 131072}); + resources.ApplyResources(this.endTransitionScaleXNumericUpDownWheel, "endTransitionScaleXNumericUpDownWheel"); + this.endTransitionScaleXNumericUpDownWheel.Name = "endTransitionScaleXNumericUpDownWheel"; + // + // endTransitionScaleLabel + // + resources.ApplyResources(this.endTransitionScaleLabel, "endTransitionScaleLabel"); + this.endTransitionScaleLabel.Name = "endTransitionScaleLabel"; + this.configurationToolTip.SetToolTip(this.endTransitionScaleLabel, resources.GetString("endTransitionScaleLabel.ToolTip")); + // + // endTransitionTimeLabel + // + resources.ApplyResources(this.endTransitionTimeLabel, "endTransitionTimeLabel"); + this.endTransitionTimeLabel.Name = "endTransitionTimeLabel"; + this.configurationToolTip.SetToolTip(this.endTransitionTimeLabel, resources.GetString("endTransitionTimeLabel.ToolTip")); + // + // endTransitionTimeNumericUpDownWheel + // + this.endTransitionTimeNumericUpDownWheel.DecimalPlaces = 2; + this.endTransitionTimeNumericUpDownWheel.Increment = new decimal(new int[] { + 100, + 0, + 0, + 0}); + resources.ApplyResources(this.endTransitionTimeNumericUpDownWheel, "endTransitionTimeNumericUpDownWheel"); + this.endTransitionTimeNumericUpDownWheel.Maximum = new decimal(new int[] { + 604800000, + 0, + 0, + 0}); + this.endTransitionTimeNumericUpDownWheel.Name = "endTransitionTimeNumericUpDownWheel"; + // + // horizontalSplitterLabel + // + resources.ApplyResources(this.horizontalSplitterLabel, "horizontalSplitterLabel"); + this.horizontalSplitterLabel.BorderStyle = System.Windows.Forms.BorderStyle.Fixed3D; + this.horizontalSplitterLabel.Name = "horizontalSplitterLabel"; + // + // endTransitionEnabledCheckBox + // + resources.ApplyResources(this.endTransitionEnabledCheckBox, "endTransitionEnabledCheckBox"); + this.endTransitionEnabledCheckBox.Name = "endTransitionEnabledCheckBox"; + this.configurationToolTip.SetToolTip(this.endTransitionEnabledCheckBox, resources.GetString("endTransitionEnabledCheckBox.ToolTip")); + this.endTransitionEnabledCheckBox.UseVisualStyleBackColor = true; + // + // endScaleYNumericUpDownWheel + // + this.endScaleYNumericUpDownWheel.DecimalPlaces = 5; + this.endScaleYNumericUpDownWheel.Increment = new decimal(new int[] { + 1, + 0, + 0, + 131072}); + resources.ApplyResources(this.endScaleYNumericUpDownWheel, "endScaleYNumericUpDownWheel"); + this.endScaleYNumericUpDownWheel.Name = "endScaleYNumericUpDownWheel"; + // + // endScaleLabel + // + resources.ApplyResources(endScaleLabel, "endScaleLabel"); + endScaleLabel.Name = "endScaleLabel"; + this.configurationToolTip.SetToolTip(endScaleLabel, resources.GetString("endScaleLabel.ToolTip")); + // + // endScaleXNumericUpDownWheel + // + this.endScaleXNumericUpDownWheel.DecimalPlaces = 5; + this.endScaleXNumericUpDownWheel.Increment = new decimal(new int[] { + 1, + 0, + 0, + 131072}); + resources.ApplyResources(this.endScaleXNumericUpDownWheel, "endScaleXNumericUpDownWheel"); + this.endScaleXNumericUpDownWheel.Name = "endScaleXNumericUpDownWheel"; + // + // yCoordinateLabel + // + resources.ApplyResources(this.yCoordinateLabel, "yCoordinateLabel"); + this.yCoordinateLabel.Name = "yCoordinateLabel"; + // + // xCoordinateLabel + // + resources.ApplyResources(this.xCoordinateLabel, "xCoordinateLabel"); + this.xCoordinateLabel.Name = "xCoordinateLabel"; + // + // startScaleYNumericUpDownWheel + // + this.startScaleYNumericUpDownWheel.DecimalPlaces = 5; + this.startScaleYNumericUpDownWheel.Increment = new decimal(new int[] { + 1, + 0, + 0, + 131072}); + resources.ApplyResources(this.startScaleYNumericUpDownWheel, "startScaleYNumericUpDownWheel"); + this.startScaleYNumericUpDownWheel.Minimum = new decimal(new int[] { + 100, + 0, + 0, + -2147483648}); + this.startScaleYNumericUpDownWheel.Name = "startScaleYNumericUpDownWheel"; + // + // startScaleLabel + // + resources.ApplyResources(startScaleLabel, "startScaleLabel"); + startScaleLabel.Name = "startScaleLabel"; + this.configurationToolTip.SetToolTip(startScaleLabel, resources.GetString("startScaleLabel.ToolTip")); + // + // startScaleXNumericUpDownWheel + // + this.startScaleXNumericUpDownWheel.DecimalPlaces = 5; + this.startScaleXNumericUpDownWheel.Increment = new decimal(new int[] { + 1, + 0, + 0, + 131072}); + resources.ApplyResources(this.startScaleXNumericUpDownWheel, "startScaleXNumericUpDownWheel"); + this.startScaleXNumericUpDownWheel.Minimum = new decimal(new int[] { + 100, + 0, + 0, + -2147483648}); + this.startScaleXNumericUpDownWheel.Name = "startScaleXNumericUpDownWheel"; + // + // transitionTimeLabel + // + resources.ApplyResources(transitionTimeLabel, "transitionTimeLabel"); + transitionTimeLabel.Name = "transitionTimeLabel"; + this.configurationToolTip.SetToolTip(transitionTimeLabel, resources.GetString("transitionTimeLabel.ToolTip")); + // + // transitionTimeNumericUpDownWheel + // + this.transitionTimeNumericUpDownWheel.DecimalPlaces = 2; + this.transitionTimeNumericUpDownWheel.Increment = new decimal(new int[] { + 100, + 0, + 0, + 0}); + resources.ApplyResources(this.transitionTimeNumericUpDownWheel, "transitionTimeNumericUpDownWheel"); + this.transitionTimeNumericUpDownWheel.Maximum = new decimal(new int[] { + 604800000, + 0, + 0, + 0}); + this.transitionTimeNumericUpDownWheel.Name = "transitionTimeNumericUpDownWheel"; + // + // panel2 + // + panel2.Controls.Add(this.okButton); + panel2.Controls.Add(this.cancelButton); + resources.ApplyResources(panel2, "panel2"); + panel2.Name = "panel2"; + // + // okButton + // + resources.ApplyResources(this.okButton, "okButton"); + this.okButton.Name = "okButton"; + this.configurationToolTip.SetToolTip(this.okButton, resources.GetString("okButton.ToolTip")); + this.okButton.UseVisualStyleBackColor = true; + // + // cancelButton + // + resources.ApplyResources(this.cancelButton, "cancelButton"); + this.cancelButton.DialogResult = System.Windows.Forms.DialogResult.Cancel; + this.cancelButton.Name = "cancelButton"; + this.configurationToolTip.SetToolTip(this.cancelButton, resources.GetString("cancelButton.ToolTip")); + this.cancelButton.UseVisualStyleBackColor = true; + // + // panel1 + // + panel1.Controls.Add(configurationGroupBox); + resources.ApplyResources(panel1, "panel1"); + panel1.Name = "panel1"; + // + // ScaleChangeBehaviorForm + // + this.AcceptButton = this.okButton; + resources.ApplyResources(this, "$this"); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.CancelButton = this.cancelButton; + this.Controls.Add(panel1); + this.Controls.Add(panel2); + this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedToolWindow; + this.MaximizeBox = false; + this.MinimizeBox = false; + this.Name = "ScaleChangeBehaviorForm"; + this.ShowIcon = false; + this.ShowInTaskbar = false; + this.SizeGripStyle = System.Windows.Forms.SizeGripStyle.Hide; + configurationGroupBox.ResumeLayout(false); + configurationGroupBox.PerformLayout(); + ((System.ComponentModel.ISupportInitialize)(this.endTransitionScaleYNumericUpDownWheel)).EndInit(); + ((System.ComponentModel.ISupportInitialize)(this.endTransitionScaleXNumericUpDownWheel)).EndInit(); + ((System.ComponentModel.ISupportInitialize)(this.endTransitionTimeNumericUpDownWheel)).EndInit(); + ((System.ComponentModel.ISupportInitialize)(this.endScaleYNumericUpDownWheel)).EndInit(); + ((System.ComponentModel.ISupportInitialize)(this.endScaleXNumericUpDownWheel)).EndInit(); + ((System.ComponentModel.ISupportInitialize)(this.startScaleYNumericUpDownWheel)).EndInit(); + ((System.ComponentModel.ISupportInitialize)(this.startScaleXNumericUpDownWheel)).EndInit(); + ((System.ComponentModel.ISupportInitialize)(this.transitionTimeNumericUpDownWheel)).EndInit(); + panel2.ResumeLayout(false); + panel1.ResumeLayout(false); + this.ResumeLayout(false); + + } + + #endregion + + private System.Windows.Forms.Button okButton; + private System.Windows.Forms.ToolTip configurationToolTip; + private System.Windows.Forms.Button cancelButton; + private NumericUpDownWheel startScaleXNumericUpDownWheel; + private NumericUpDownWheel transitionTimeNumericUpDownWheel; + private NumericUpDownWheel endScaleYNumericUpDownWheel; + private NumericUpDownWheel endScaleXNumericUpDownWheel; + private System.Windows.Forms.Label yCoordinateLabel; + private System.Windows.Forms.Label xCoordinateLabel; + private NumericUpDownWheel startScaleYNumericUpDownWheel; + private System.Windows.Forms.Label endTransitionTimeLabel; + private NumericUpDownWheel endTransitionTimeNumericUpDownWheel; + private System.Windows.Forms.Label horizontalSplitterLabel; + private System.Windows.Forms.CheckBox endTransitionEnabledCheckBox; + private System.Windows.Forms.Label yCoordinateEndTransitionLabel; + private System.Windows.Forms.Label xCoordinateEndTransitionLabel; + private NumericUpDownWheel endTransitionScaleYNumericUpDownWheel; + private NumericUpDownWheel endTransitionScaleXNumericUpDownWheel; + private System.Windows.Forms.Label endTransitionScaleLabel; + } +} \ No newline at end of file diff --git a/src/EgamiFlowScreensaver/Config/ScaleChangeBehaviorForm.cs b/src/EgamiFlowScreensaver/Config/ScaleChangeBehaviorForm.cs new file mode 100644 index 0000000..22ae754 --- /dev/null +++ b/src/EgamiFlowScreensaver/Config/ScaleChangeBehaviorForm.cs @@ -0,0 +1,136 @@ +// +// Copyright (c) Adrian John Dunstan. All rights reserved. +// +// Licensed 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. +// + +namespace Natsnudasoft.EgamiFlowScreensaver.Config +{ + using System; + using System.Windows.Forms; + using Natsnudasoft.NatsnudaLibrary; + + /// + /// A form allowing configuration of a behaviour that causes an image item to transition from + /// one alpha scale to another over a specified time frame. + /// + /// + public partial class ScaleChangeBehaviorForm : Form + { + private readonly ScaleChangeBehaviorFormViewModel viewModel; + + /// + /// Initializes a new instance of the class. + /// + /// The view model to bind to. + /// is + /// . + public ScaleChangeBehaviorForm(ScaleChangeBehaviorFormViewModel viewModel) + { + ParameterValidation.IsNotNull(viewModel, nameof(viewModel)); + + this.viewModel = viewModel; + this.InitializeComponent(); + + this.startScaleXNumericUpDownWheel.DataBindings.Add( + nameof(NumericUpDown.Value), + this.viewModel, + nameof(ScaleChangeBehaviorFormViewModel.StartScaleX), + true); + + this.startScaleYNumericUpDownWheel.DataBindings.Add( + nameof(NumericUpDown.Value), + this.viewModel, + nameof(ScaleChangeBehaviorFormViewModel.StartScaleY), + true); + + this.endScaleXNumericUpDownWheel.DataBindings.Add( + nameof(NumericUpDown.Value), + this.viewModel, + nameof(ScaleChangeBehaviorFormViewModel.EndScaleX), + true); + + this.endScaleYNumericUpDownWheel.DataBindings.Add( + nameof(NumericUpDown.Value), + this.viewModel, + nameof(ScaleChangeBehaviorFormViewModel.EndScaleY), + true); + + this.transitionTimeNumericUpDownWheel.DataBindings.Add( + nameof(NumericUpDown.Value), + this.viewModel, + nameof(ScaleChangeBehaviorFormViewModel.TransitionTime), + true); + + this.endTransitionEnabledCheckBox.DataBindings.Add( + nameof(CheckBox.Enabled), + this.viewModel, + nameof(ScaleChangeBehaviorFormViewModel.IsInfiniteImageEmitMode)); + this.endTransitionEnabledCheckBox.DataBindings.Add( + nameof(CheckBox.Checked), + this.viewModel, + nameof(ScaleChangeBehaviorFormViewModel.EndTransitionEnabled), + false, + DataSourceUpdateMode.OnPropertyChanged); + + this.endTransitionScaleLabel.DataBindings.Add( + nameof(Label.Enabled), + this.viewModel, + nameof(AlphaChangeBehaviorFormViewModel.EndTransitionEnabled)); + + this.endTransitionScaleXNumericUpDownWheel.DataBindings.Add( + nameof(NumericUpDownWheel.Enabled), + this.viewModel, + nameof(ScaleChangeBehaviorFormViewModel.EndTransitionEnabled)); + this.endTransitionScaleXNumericUpDownWheel.DataBindings.Add( + nameof(NumericUpDownWheel.Value), + this.viewModel, + nameof(ScaleChangeBehaviorFormViewModel.EndTransitionScaleX), + true); + + this.endTransitionScaleYNumericUpDownWheel.DataBindings.Add( + nameof(NumericUpDownWheel.Enabled), + this.viewModel, + nameof(ScaleChangeBehaviorFormViewModel.EndTransitionEnabled)); + this.endTransitionScaleYNumericUpDownWheel.DataBindings.Add( + nameof(NumericUpDownWheel.Value), + this.viewModel, + nameof(ScaleChangeBehaviorFormViewModel.EndTransitionScaleY), + true); + + this.endTransitionTimeLabel.DataBindings.Add( + nameof(Label.Enabled), + this.viewModel, + nameof(ScaleChangeBehaviorFormViewModel.EndTransitionEnabled)); + + this.endTransitionTimeNumericUpDownWheel.DataBindings.Add( + nameof(NumericUpDownWheel.Enabled), + this.viewModel, + nameof(ScaleChangeBehaviorFormViewModel.EndTransitionEnabled)); + this.endTransitionTimeNumericUpDownWheel.DataBindings.Add( + nameof(NumericUpDownWheel.Value), + this.viewModel, + nameof(ScaleChangeBehaviorFormViewModel.EndTransitionTime), + true); + + this.okButton.Click += (sender, e) => + { + if (this.viewModel.Validate(this)) + { + this.okButton.Focus(); + this.DialogResult = DialogResult.OK; + } + }; + } + } +} \ No newline at end of file diff --git a/src/EgamiFlowScreensaver/Config/ScaleChangeBehaviorForm.resx b/src/EgamiFlowScreensaver/Config/ScaleChangeBehaviorForm.resx new file mode 100644 index 0000000..81cc4a7 --- /dev/null +++ b/src/EgamiFlowScreensaver/Config/ScaleChangeBehaviorForm.resx @@ -0,0 +1,909 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + False + + + + Top, Bottom, Left, Right + + + NoControl + + + + 210, 180 + + + 75, 20 + + + + 13 + + + y + + + MiddleCenter + + + yCoordinateEndTransitionLabel + + + System.Windows.Forms.Label, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + configurationGroupBox + + + 0 + + + NoControl + + + 123, 180 + + + 75, 20 + + + 12 + + + x + + + MiddleCenter + + + xCoordinateEndTransitionLabel + + + System.Windows.Forms.Label, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + configurationGroupBox + + + 1 + + + 210, 206 + + + 6, 6, 6, 6 + + + 75, 20 + + + 16 + + + endTransitionScaleYNumericUpDownWheel + + + Natsnudasoft.EgamiFlowScreensaver.NumericUpDownWheel, Natsnudasoft.EgamiFlowScreensaver, Version=0.1.0.0, Culture=neutral, PublicKeyToken=null + + + configurationGroupBox + + + 2 + + + 123, 206 + + + 6, 6, 6, 6 + + + 75, 20 + + + 15 + + + endTransitionScaleXNumericUpDownWheel + + + Natsnudasoft.EgamiFlowScreensaver.NumericUpDownWheel, Natsnudasoft.EgamiFlowScreensaver, Version=0.1.0.0, Culture=neutral, PublicKeyToken=null + + + configurationGroupBox + + + 3 + + + True + + + NoControl + + + 6, 208 + + + 6, 0, 6, 0 + + + 105, 13 + + + 14 + + + End Transition Scale + + + MiddleLeft + + + 17, 17 + + + The scale value (along x and y axis) that the end transition will finish at (starting from the end scale value). + + + endTransitionScaleLabel + + + System.Windows.Forms.Label, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + configurationGroupBox + + + 4 + + + True + + + NoControl + + + 6, 240 + + + 6, 0, 6, 0 + + + 101, 13 + + + 17 + + + End Transition Time + + + MiddleLeft + + + The amount of time (in milliseconds) that the end transition will take. This time extends the original image lifetime. + + + endTransitionTimeLabel + + + System.Windows.Forms.Label, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + configurationGroupBox + + + 5 + + + 119, 238 + + + 6, 6, 6, 6 + + + 92, 20 + + + 18 + + + endTransitionTimeNumericUpDownWheel + + + Natsnudasoft.EgamiFlowScreensaver.NumericUpDownWheel, Natsnudasoft.EgamiFlowScreensaver, Version=0.1.0.0, Culture=neutral, PublicKeyToken=null + + + configurationGroupBox + + + 6 + + + Top, Left, Right + + + NoControl + + + 0, 143 + + + 0, 6, 0, 6 + + + 295, 2 + + + 10 + + + horizontalSplitterLabel + + + System.Windows.Forms.Label, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + configurationGroupBox + + + 7 + + + Top + + + NoControl + + + 84, 157 + + + 6, 6, 6, 6 + + + 136, 17 + + + 11 + + + End Transition Enabled + + + Whether or not the end transition will play when emitted images reach the end of their lifetime. + + + endTransitionEnabledCheckBox + + + System.Windows.Forms.CheckBox, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + configurationGroupBox + + + 8 + + + 164, 79 + + + 6, 6, 6, 6 + + + 75, 20 + + + 7 + + + endScaleYNumericUpDownWheel + + + Natsnudasoft.EgamiFlowScreensaver.NumericUpDownWheel, Natsnudasoft.EgamiFlowScreensaver, Version=0.1.0.0, Culture=neutral, PublicKeyToken=null + + + configurationGroupBox + + + 9 + + + False + + + True + + + NoControl + + + 6, 81 + + + 6, 0, 6, 0 + + + 56, 13 + + + 5 + + + End Scale + + + MiddleLeft + + + The scale value (along x and y axis) that this transition will finish at. + + + endScaleLabel + + + System.Windows.Forms.Label, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + configurationGroupBox + + + 10 + + + 77, 79 + + + 6, 6, 6, 6 + + + 75, 20 + + + 6 + + + endScaleXNumericUpDownWheel + + + Natsnudasoft.EgamiFlowScreensaver.NumericUpDownWheel, Natsnudasoft.EgamiFlowScreensaver, Version=0.1.0.0, Culture=neutral, PublicKeyToken=null + + + configurationGroupBox + + + 11 + + + NoControl + + + 164, 21 + + + 75, 20 + + + 1 + + + y + + + MiddleCenter + + + yCoordinateLabel + + + System.Windows.Forms.Label, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + configurationGroupBox + + + 12 + + + NoControl + + + 77, 21 + + + 75, 20 + + + 0 + + + x + + + MiddleCenter + + + xCoordinateLabel + + + System.Windows.Forms.Label, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + configurationGroupBox + + + 13 + + + 164, 47 + + + 6, 6, 6, 6 + + + 75, 20 + + + 4 + + + startScaleYNumericUpDownWheel + + + Natsnudasoft.EgamiFlowScreensaver.NumericUpDownWheel, Natsnudasoft.EgamiFlowScreensaver, Version=0.1.0.0, Culture=neutral, PublicKeyToken=null + + + configurationGroupBox + + + 14 + + + False + + + True + + + NoControl + + + 6, 49 + + + 6, 0, 6, 0 + + + 59, 13 + + + 2 + + + Start Scale + + + MiddleLeft + + + The scale value (along x and y axis) that this transition will start from. These values can be less than 0 to delay the start of the transition. + + + startScaleLabel + + + System.Windows.Forms.Label, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + configurationGroupBox + + + 15 + + + 77, 47 + + + 6, 6, 6, 6 + + + 75, 20 + + + 3 + + + startScaleXNumericUpDownWheel + + + Natsnudasoft.EgamiFlowScreensaver.NumericUpDownWheel, Natsnudasoft.EgamiFlowScreensaver, Version=0.1.0.0, Culture=neutral, PublicKeyToken=null + + + configurationGroupBox + + + 16 + + + False + + + True + + + NoControl + + + 6, 113 + + + 6, 0, 6, 0 + + + 79, 13 + + + 8 + + + Transition Time + + + MiddleLeft + + + The amount of time (in milliseconds) that this transition will take. + + + transitionTimeLabel + + + System.Windows.Forms.Label, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + configurationGroupBox + + + 17 + + + 97, 111 + + + 6, 6, 6, 6 + + + 92, 20 + + + 9 + + + transitionTimeNumericUpDownWheel + + + Natsnudasoft.EgamiFlowScreensaver.NumericUpDownWheel, Natsnudasoft.EgamiFlowScreensaver, Version=0.1.0.0, Culture=neutral, PublicKeyToken=null + + + configurationGroupBox + + + 18 + + + 12, 12 + + + 4, 4, 4, 4 + + + 0, 8, 0, 0 + + + 295, 269 + + + 0 + + + Configuration + + + configurationGroupBox + + + System.Windows.Forms.GroupBox, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + panel1 + + + 0 + + + False + + + Top, Left, Right + + + NoControl + + + 14, 15 + + + 6, 6, 6, 6 + + + 99, 26 + + + 0 + + + OK + + + Accept the current behaviour configuration changes. + + + okButton + + + System.Windows.Forms.Button, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + panel2 + + + 0 + + + Top, Left, Right + + + NoControl + + + 14, 53 + + + 6, 6, 6, 6 + + + 99, 26 + + + 1 + + + Cancel + + + Close the behaviour configuration without saving changes. + + + cancelButton + + + System.Windows.Forms.Button, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + panel2 + + + 1 + + + Right + + + 319, 0 + + + 0, 0, 0, 0 + + + 8, 8, 8, 8 + + + 128, 294 + + + 1 + + + panel2 + + + System.Windows.Forms.Panel, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + $this + + + 1 + + + False + + + Fill + + + 0, 0 + + + 0, 0, 0, 0 + + + 8, 8, 8, 8 + + + 319, 294 + + + 7 + + + panel1 + + + System.Windows.Forms.Panel, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + $this + + + 0 + + + True + + + 6, 13 + + + 447, 294 + + + CenterParent + + + Scale Change Behaviour Configuration + + + configurationToolTip + + + System.Windows.Forms.ToolTip, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + ScaleChangeBehaviorForm + + + System.Windows.Forms.Form, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/src/EgamiFlowScreensaver/Config/ScaleChangeBehaviorFormViewModel.cs b/src/EgamiFlowScreensaver/Config/ScaleChangeBehaviorFormViewModel.cs new file mode 100644 index 0000000..e0926ac --- /dev/null +++ b/src/EgamiFlowScreensaver/Config/ScaleChangeBehaviorFormViewModel.cs @@ -0,0 +1,221 @@ +// +// Copyright (c) Adrian John Dunstan. All rights reserved. +// +// Licensed 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. +// + +namespace Natsnudasoft.EgamiFlowScreensaver.Config +{ + using System; + using System.Windows.Forms; + using Natsnudasoft.EgamiFlowScreensaver.Properties; + using Natsnudasoft.NatsnudaLibrary; + + /// + /// Provides a class for managing the state of the backing values for the current state of + /// configuration for a scale change behaviour. + /// + /// + public sealed class ScaleChangeBehaviorFormViewModel : ConfigurationBehaviorFormViewModel + { + private readonly ILifetimeDetails lifetimeDetails; + private float startScaleX; + private float startScaleY; + private float endScaleX; + private float endScaleY; + private double transitionTime; + private bool endTransitionEnabled; + private float endTransitionScaleX; + private float endTransitionScaleY; + private double endTransitionTime; + + /// + /// Initializes a new instance of the class. + /// + /// The current lifetime settings of any images emitted. + /// + /// is + /// . + public ScaleChangeBehaviorFormViewModel(ILifetimeDetails lifetimeDetails) + { + ParameterValidation.IsNotNull(lifetimeDetails, nameof(lifetimeDetails)); + + this.lifetimeDetails = lifetimeDetails; + } + + /// + /// Gets or sets the scale along the x axis that the behaviour should start from. + /// + public float StartScaleX + { + get => this.startScaleX; + set => this.Set(ref this.startScaleX, value); + } + + /// + /// Gets or sets the scale along the y axis that the behaviour should start from. + /// + public float StartScaleY + { + get => this.startScaleY; + set => this.Set(ref this.startScaleY, value); + } + + /// + /// Gets or sets the scale along the x axis that the behaviour should finish at. + /// + public float EndScaleX + { + get => this.endScaleX; + set => this.Set(ref this.endScaleX, value); + } + + /// + /// Gets or sets the scale along the y axis that the behaviour should finish at. + /// + public float EndScaleY + { + get => this.endScaleY; + set => this.Set(ref this.endScaleY, value); + } + + /// + /// Gets or sets the time that the behaviour transition should take (in milliseconds). + /// + public double TransitionTime + { + get => this.transitionTime; + set => this.Set(ref this.transitionTime, value); + } + + /// + /// Gets a value indicating whether or not images will be emitted infinitely. + /// + public bool IsInfiniteImageEmitMode + { + get => this.lifetimeDetails.IsInfiniteImageEmitMode; + } + + /// + /// Gets or sets a value indicating whether or not the ending transition will be enabled for + /// the image item this behaviour is attached to. + /// + /// if the ending transition will be enabled for the image + /// item this behaviour is attached to; otherwise . + public bool EndTransitionEnabled + { + get => this.endTransitionEnabled; + set => this.Set(ref this.endTransitionEnabled, value && this.IsInfiniteImageEmitMode); + } + + /// + /// Gets or sets the scale value along the x axis that the behaviour will finish at when the + /// image item it is attached to is being destroyed. + /// + public float EndTransitionScaleX + { + get => this.endTransitionScaleX; + set => this.Set(ref this.endTransitionScaleX, value); + } + + /// + /// Gets or sets the scale value along the y axis that the behaviour will finish at when the + /// image item it is attached to is being destroyed. + /// + public float EndTransitionScaleY + { + get => this.endTransitionScaleY; + set => this.Set(ref this.endTransitionScaleY, value); + } + + /// + /// Gets or sets the time that the behaviour will take to transition when the image item + /// it is attached to is being destroyed (in milliseconds). + /// + public double EndTransitionTime + { + get => this.endTransitionTime; + set => this.Set(ref this.endTransitionTime, value); + } + + /// + /// is + /// . + /// The specified behaviour is not a valid + /// . + public override void UpdateFromBehavior(ConfigurationBehavior behavior) + { + ParameterValidation.IsNotNull(behavior, nameof(behavior)); + + if (behavior is ScaleChangeConfigurationBehavior scaleChangeConfigurationBehavior) + { + this.StartScaleX = scaleChangeConfigurationBehavior.StartScaleX; + this.StartScaleY = scaleChangeConfigurationBehavior.StartScaleY; + this.EndScaleX = scaleChangeConfigurationBehavior.EndScaleX; + this.EndScaleY = scaleChangeConfigurationBehavior.EndScaleY; + this.TransitionTime = + scaleChangeConfigurationBehavior.TransitionTime.TotalMilliseconds; + this.EndTransitionEnabled = scaleChangeConfigurationBehavior.EndTransitionEnabled; + this.EndTransitionScaleX = scaleChangeConfigurationBehavior.EndTransitionScaleX; + this.EndTransitionScaleY = scaleChangeConfigurationBehavior.EndTransitionScaleY; + this.EndTransitionTime = + scaleChangeConfigurationBehavior.EndTransitionTime.TotalMilliseconds; + } + else + { + throw new InvalidOperationException(nameof(behavior) + " was an unexpected type."); + } + } + + /// + public override ConfigurationBehavior CreateBehavior() + { + return new ScaleChangeConfigurationBehavior + { + StartScaleX = this.StartScaleX, + StartScaleY = this.StartScaleY, + EndScaleX = this.EndScaleX, + EndScaleY = this.EndScaleY, + TransitionTime = + new TimeSpan((long)(this.TransitionTime * TimeSpan.TicksPerMillisecond)), + EndTransitionEnabled = this.EndTransitionEnabled, + EndTransitionScaleX = this.EndTransitionScaleX, + EndTransitionScaleY = this.EndTransitionScaleY, + EndTransitionTime = + new TimeSpan((long)(this.EndTransitionTime * TimeSpan.TicksPerMillisecond)) + }; + } + + /// + public override bool Validate(IWin32Window owner) + { + var validated = true; + if (this.IsInfiniteImageEmitMode && + this.TransitionTime > this.lifetimeDetails.ImageEmitLifetime) + { + if (MessageBox.Show( + owner, + Resources.TransitionTimeLessThanLifetimeText, + Resources.TransitionTimeLessThanLifetimeCaption, + MessageBoxButtons.YesNo, + MessageBoxIcon.Question, + MessageBoxDefaultButton.Button2) == DialogResult.No) + { + validated = false; + } + } + + return validated; + } + } +} \ No newline at end of file diff --git a/src/EgamiFlowScreensaver/Config/ScaleChangeConfigurationBehavior.cs b/src/EgamiFlowScreensaver/Config/ScaleChangeConfigurationBehavior.cs new file mode 100644 index 0000000..5cf1234 --- /dev/null +++ b/src/EgamiFlowScreensaver/Config/ScaleChangeConfigurationBehavior.cs @@ -0,0 +1,123 @@ +// +// Copyright (c) Adrian John Dunstan. All rights reserved. +// +// Licensed 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. +// + +namespace Natsnudasoft.EgamiFlowScreensaver.Config +{ + using System; + using System.ComponentModel; + using Natsnudasoft.EgamiFlowScreensaver.Properties; + using ProtoBuf; + + /// + /// Provides a class for managing the current state of configuration for a scale change + /// behaviour. + /// + /// + [ProtoContract] + public sealed class ScaleChangeConfigurationBehavior : ConfigurationBehavior + { + private const long DefaultTransitionTime = 1000 * TimeSpan.TicksPerMillisecond; + private const long DefaultEndTransitionTime = 750 * TimeSpan.TicksPerMillisecond; + + /// + public override ConfigurationBehaviorType ConfigurationBehaviorType + { + get => ConfigurationBehaviorType.ScaleChange; + } + + /// + public override string Description + { + get => Resources.ScaleChangeBehaviorDescription; + } + + /// + /// Gets or sets the scale along the x axis that the behaviour should start from. + /// + [ProtoMember(1)] + public float StartScaleX { get; set; } + + /// + /// Gets or sets the scale along the y axis that the behaviour should start from. + /// + [ProtoMember(2)] + public float StartScaleY { get; set; } + + /// + /// Gets or sets the scale along the x axis that the behaviour should finish at. + /// + [ProtoMember(3)] + [DefaultValue(1f)] + public float EndScaleX { get; set; } = 1f; + + /// + /// Gets or sets the scale along the y axis that the behaviour should finish at. + /// + [ProtoMember(4)] + [DefaultValue(1f)] + public float EndScaleY { get; set; } = 1f; + + /// + /// Gets or sets the time that the behaviour transition should take. + /// + public TimeSpan TransitionTime { get; set; } = new TimeSpan(DefaultTransitionTime); + + /// + /// Gets or sets a value indicating whether or not the ending transition will be enabled for + /// the image item this behaviour is attached to. + /// + /// if the ending transition will be enabled for the image + /// item this behaviour is attached to; otherwise . + [ProtoMember(6, IsRequired = false)] + public bool EndTransitionEnabled { get; set; } + + /// + /// Gets or sets the scale value along the x axis that the behaviour will finish at when the + /// image item it is attached to is being destroyed. + /// + [ProtoMember(7, IsRequired = false)] + public float EndTransitionScaleX { get; set; } + + /// + /// Gets or sets the scale value along the y axis that the behaviour will finish at when the + /// image item it is attached to is being destroyed. + /// + [ProtoMember(8, IsRequired = false)] + public float EndTransitionScaleY { get; set; } + + /// + /// Gets or sets the time that the behaviour will take to transition when the image item + /// it is attached to is being destroyed. + /// + public TimeSpan EndTransitionTime { get; set; } = new TimeSpan(DefaultEndTransitionTime); + + [ProtoMember(5)] + [DefaultValue(DefaultTransitionTime)] + private long TransitionTimeSerialized + { + get => this.TransitionTime.Ticks; + set => this.TransitionTime = new TimeSpan(value); + } + + [ProtoMember(9, IsRequired = false)] + [DefaultValue(DefaultEndTransitionTime)] + private long EndTransitionTimeSerialized + { + get => this.EndTransitionTime.Ticks; + set => this.EndTransitionTime = new TimeSpan(value); + } + } +} \ No newline at end of file diff --git a/src/EgamiFlowScreensaver/Config/ScreensaverConfiguration.cs b/src/EgamiFlowScreensaver/Config/ScreensaverConfiguration.cs index 09a115f..4718e3b 100644 --- a/src/EgamiFlowScreensaver/Config/ScreensaverConfiguration.cs +++ b/src/EgamiFlowScreensaver/Config/ScreensaverConfiguration.cs @@ -16,6 +16,7 @@ namespace Natsnudasoft.EgamiFlowScreensaver.Config { + using System; using System.Collections.Generic; using System.ComponentModel; using System.Drawing; @@ -27,12 +28,15 @@ namespace Natsnudasoft.EgamiFlowScreensaver.Config [ProtoContract] public sealed class ScreensaverConfiguration { + private const long DefaultImageEmitLifetime = 20000 * TimeSpan.TicksPerMillisecond; + /// /// Initializes a new instance of the class. /// public ScreensaverConfiguration() { this.Images = new List(); + this.Behaviors = new List(); } /// @@ -93,11 +97,55 @@ public ScreensaverConfiguration() [DefaultValue(ImageEmitLocation.RandomCorner)] public ImageEmitLocation ImageEmitLocation { get; set; } + /// + /// Gets or sets the X coordinate of the emit location if it is in + /// mode. + /// + [ProtoMember(9, IsRequired = false)] + public int CustomImageEmitLocationX { get; set; } + + /// + /// Gets or sets the Y coordinate of the emit location if it is in + /// mode. + /// + [ProtoMember(10, IsRequired = false)] + public int CustomImageEmitLocationY { get; set; } + + /// + /// Gets the collection of the configuration values for behaviours that will be applied to + /// any emitted images. + /// + [ProtoMember(11, IsRequired = false)] + public IList Behaviors { get; } + + /// + /// Gets or sets a value indicating whether or not images will be emitted infinitely. + /// + /// if images should be emitted infinitely; otherwise + /// . + [ProtoMember(12, IsRequired = false)] + public bool IsInfiniteImageEmitMode { get; set; } + + /// + /// Gets or sets the time that emitted images should live for. + /// + /// This setting is only used if is + /// . + public TimeSpan ImageEmitLifetime { get; set; } = new TimeSpan(DefaultImageEmitLifetime); + [ProtoMember(3, DataFormat = DataFormat.FixedSize)] private int BackgroundColorSerialized { get { return this.BackgroundColor.ToArgb(); } set { this.BackgroundColor = Color.FromArgb(value); } } + + [ProtoMember(13, IsRequired = false)] + [DefaultValue(DefaultImageEmitLifetime)] + private long EmitLifetimeSerialized + { + get => this.ImageEmitLifetime.Ticks; + set => this.ImageEmitLifetime = new TimeSpan(value); + } } } \ No newline at end of file diff --git a/src/EgamiFlowScreensaver/Config/ScreensaverSettingsAdapterForm.cs b/src/EgamiFlowScreensaver/Config/ScreensaverSettingsAdapterForm.cs index 7a12763..68b7337 100644 --- a/src/EgamiFlowScreensaver/Config/ScreensaverSettingsAdapterForm.cs +++ b/src/EgamiFlowScreensaver/Config/ScreensaverSettingsAdapterForm.cs @@ -25,6 +25,7 @@ namespace Natsnudasoft.EgamiFlowScreensaver.Config /// configuration form can be shown as a dialog of the window. /// /// + [System.ComponentModel.DesignerCategory("Code")] public sealed class ScreensaverSettingsAdapterForm : Form { private readonly INativeMethods nativeMethods; diff --git a/src/EgamiFlowScreensaver/CustomImageEmitDetails.cs b/src/EgamiFlowScreensaver/CustomImageEmitDetails.cs new file mode 100644 index 0000000..c545d09 --- /dev/null +++ b/src/EgamiFlowScreensaver/CustomImageEmitDetails.cs @@ -0,0 +1,116 @@ +// +// Copyright (c) Adrian John Dunstan. All rights reserved. +// +// Licensed 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. +// + +namespace Natsnudasoft.EgamiFlowScreensaver +{ + using System; + using System.Collections.Generic; + using System.Linq; + using Microsoft.Xna.Framework; + using Microsoft.Xna.Framework.Graphics; + using Natsnudasoft.EgamiFlowScreensaver.Config; + using Natsnudasoft.NatsnudaLibrary; + + /// + /// Provides a class capable of creating locations at a custom position for a + /// . + /// + public sealed class CustomImageEmitDetails : ImageEmitDetails + { + private readonly Vector2 customImageEmitLocation; + + /// + /// Initializes a new instance of the class. + /// + /// The description of the area of the screensaver. + /// The + /// representing the configured state of the screensaver. + /// A collection of factories that define how to create + /// behaviours that will be attached to any images emitted by a + /// . + /// , + /// , or + /// is . + public CustomImageEmitDetails( + ScreensaverArea screensaverArea, + ScreensaverConfiguration screensaverConfiguration, + IEnumerable> behaviorFactories) + : base(screensaverArea, screensaverConfiguration, behaviorFactories) + { + ParameterValidation.IsNotNull( + screensaverConfiguration, + nameof(screensaverConfiguration)); + + this.customImageEmitLocation = new Vector2( + screensaverConfiguration.CustomImageEmitLocationX, + screensaverConfiguration.CustomImageEmitLocationY); + } + + /// + /// Initializes a new instance of the class. + /// + /// The description of the area of the screensaver. + /// The + /// representing the configured state of the screensaver. + /// A collection of factories that define how to create + /// behaviours that will be attached to any images emitted by a + /// . + /// A pseudo-random number generator that can be used to generate + /// randomness in the . + /// , + /// , , or + /// is . + public CustomImageEmitDetails( + ScreensaverArea screensaverArea, + ScreensaverConfiguration screensaverConfiguration, + IEnumerable> behaviorFactories, + Random random) + : base(screensaverArea, screensaverConfiguration, behaviorFactories, random) + { + ParameterValidation.IsNotNull( + screensaverConfiguration, + nameof(screensaverConfiguration)); + + this.customImageEmitLocation = new Vector2( + screensaverConfiguration.CustomImageEmitLocationX, + screensaverConfiguration.CustomImageEmitLocationY); + } + + /// + /// is + /// . + public override ScreensaverImageItem CreateScreensaverImageItem(Texture2D texture) + { + ParameterValidation.IsNotNull(texture, nameof(texture)); + + var origin = new Vector2(texture.Width, texture.Height) / 2f; + var minX = this.customImageEmitLocation.X - PositionDistribution + origin.X; + var maxX = this.customImageEmitLocation.X + PositionDistribution + origin.X; + var minY = this.customImageEmitLocation.Y - PositionDistribution + origin.Y; + var maxY = this.customImageEmitLocation.Y + PositionDistribution + origin.Y; + var position = new Vector2( + this.Random.NextFloat(minX, maxX), + this.Random.NextFloat(minY, maxY)); + return new ScreensaverImageItem( + texture, + this.BehaviorFactories.Select(f => f?.Invoke())) + { + Position = position, + Origin = origin + }; + } + } +} \ No newline at end of file diff --git a/src/EgamiFlowScreensaver/DataSourceDisplayValue.cs b/src/EgamiFlowScreensaver/DataSourceDisplayValue.cs new file mode 100644 index 0000000..c1c80fd --- /dev/null +++ b/src/EgamiFlowScreensaver/DataSourceDisplayValue.cs @@ -0,0 +1,51 @@ +// +// Copyright (c) Adrian John Dunstan. All rights reserved. +// +// Licensed 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. +// + +namespace Natsnudasoft.EgamiFlowScreensaver +{ + using System; + + /// + /// Provides a simple class that can be used to provide a display value and underlying value as + /// a binding source. + /// + /// The that the display value of this data source + /// is representing. + public sealed class DataSourceDisplayValue + { + /// + /// Initializes a new instance of the class. + /// + /// The display value that represents how to display the + /// specified value. + /// The underlying value that will be used as a binding source. + public DataSourceDisplayValue(string displayValue, T value) + { + this.DisplayValue = displayValue; + this.Value = value; + } + + /// + /// Gets the display value that represents how to display the value of this data source. + /// + public string DisplayValue { get; } + + /// + /// Gets the underlying value that is to be used as a binding source. + /// + public T Value { get; } + } +} \ No newline at end of file diff --git a/src/EgamiFlowScreensaver/EgamiFlowScreensaver.csproj b/src/EgamiFlowScreensaver/EgamiFlowScreensaver.csproj index 94d59bb..5939660 100644 --- a/src/EgamiFlowScreensaver/EgamiFlowScreensaver.csproj +++ b/src/EgamiFlowScreensaver/EgamiFlowScreensaver.csproj @@ -8,7 +8,6 @@ - Debug AnyCPU @@ -73,6 +72,38 @@ + + + + + + + + Form + + + AlphaChangeBehaviorForm.cs + + + + Form + + + ApplyBehaviorsForm.cs + + + + + + Form + + + ColorChangeBehaviorForm.cs + + + + + @@ -84,17 +115,60 @@ + + + Form + + + CustomEmitLocationForm.cs + + + + + + + Form + + + RotationChangeBehaviorForm.cs + + + + + Form + + + ScaleChangeBehaviorForm.cs + + + + + + + + + + + + + + + + + + + @@ -102,9 +176,7 @@ Component - - Form - + @@ -131,6 +203,7 @@ + @@ -138,6 +211,9 @@ + + + Content\adie.xnb @@ -155,8 +231,8 @@ ..\..\packages\Costura.Fody.3.3.1\lib\net40\Costura.dll - - ..\..\packages\MonoGame.Framework.WindowsDX.3.7.1.189\lib\net45\MonoGame.Framework.dll + + ..\..\packages\MonoGame.Framework.WindowsDX.3.6.0.1625\lib\net40\MonoGame.Framework.dll ..\..\packages\NatsnudaLibrary.0.5.2\lib\net46\Natsnudasoft.NatsnudaLibrary.dll @@ -167,33 +243,6 @@ ..\..\packages\protobuf-net.2.4.0\lib\net40\protobuf-net.dll - - ..\..\packages\SharpDX.4.2.0\lib\net45\SharpDX.dll - - - ..\..\packages\SharpDX.Direct2D1.4.2.0\lib\net45\SharpDX.Direct2D1.dll - - - ..\..\packages\SharpDX.Direct3D11.4.2.0\lib\net45\SharpDX.Direct3D11.dll - - - ..\..\packages\SharpDX.Direct3D9.4.2.0\lib\net45\SharpDX.Direct3D9.dll - - - ..\..\packages\SharpDX.DXGI.4.2.0\lib\net45\SharpDX.DXGI.dll - - - ..\..\packages\SharpDX.Mathematics.4.2.0\lib\net45\SharpDX.Mathematics.dll - - - ..\..\packages\SharpDX.MediaFoundation.4.2.0\lib\net45\SharpDX.MediaFoundation.dll - - - ..\..\packages\SharpDX.XAudio2.4.2.0\lib\net45\SharpDX.XAudio2.dll - - - ..\..\packages\SharpDX.XInput.4.2.0\lib\net45\SharpDX.XInput.dll - @@ -219,6 +268,27 @@ + + AlphaChangeBehaviorForm.cs + + + ApplyBehaviorsForm.cs + Designer + + + ColorChangeBehaviorForm.cs + Designer + + + CustomEmitLocationForm.cs + Designer + + + RotationChangeBehaviorForm.cs + + + ScaleChangeBehaviorForm.cs + Designer @@ -276,7 +346,6 @@ - diff --git a/src/EgamiFlowScreensaver/EnabledBehaviorFactoriesFactory.cs b/src/EgamiFlowScreensaver/EnabledBehaviorFactoriesFactory.cs new file mode 100644 index 0000000..2437407 --- /dev/null +++ b/src/EgamiFlowScreensaver/EnabledBehaviorFactoriesFactory.cs @@ -0,0 +1,270 @@ +// +// Copyright (c) Adrian John Dunstan. All rights reserved. +// +// Licensed 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. +// + +namespace Natsnudasoft.EgamiFlowScreensaver +{ + using System; + using System.Collections.Generic; + using System.Linq; + using Microsoft.Xna.Framework; + using Natsnudasoft.EgamiFlowScreensaver.Config; + using Natsnudasoft.NatsnudaLibrary; + + /// + /// Provides a factory class that when the associated configuration behaviour is enabled will + /// create factories that are capable of creating behaviours that can be applied to images that + /// are emitted by a screensaver. + /// + /// + public sealed class EnabledBehaviorFactoriesFactory : IBehaviorFactoriesFactory + { + private readonly Random random; + + /// + /// Initializes a new instance of the class. + /// + /// A pseudo-random number generator that can be used to generate + /// randomness in the behaviour factories generated by this factory. + /// is + /// . + public EnabledBehaviorFactoriesFactory(Random random) + { + ParameterValidation.IsNotNull(random, nameof(random)); + + this.random = random; + } + + /// + /// , or + /// is . + public IEnumerable> Create( + ScreensaverArea screensaverArea, + IEnumerable configurationBehaviors, + bool isInfiniteImageEmitMode) + { + ParameterValidation.IsNotNull(screensaverArea, nameof(screensaverArea)); + ParameterValidation.IsNotNull(configurationBehaviors, nameof(configurationBehaviors)); + + return configurationBehaviors + .Where(b => b.Enabled) + .Select(b => CreateBehaviorFactory( + screensaverArea, + b, + isInfiniteImageEmitMode, + this.random)); + } + + private static Func CreateBehaviorFactory( + ScreensaverArea screensaverArea, + ConfigurationBehavior configurationBehavior, + bool isInfiniteImageEmitMode, + Random random) + { + Func behaviorFactory; + var behaviorType = configurationBehavior.ConfigurationBehaviorType; + switch (behaviorType) + { + case ConfigurationBehaviorType.ColorChange: + behaviorFactory = CreateColorChangeBehaviorFactory( + screensaverArea, + isInfiniteImageEmitMode, + configurationBehavior as ColorChangeConfigurationBehavior); + break; + case ConfigurationBehaviorType.ScaleChange: + behaviorFactory = CreateScaleChangeBehaviorFactory( + screensaverArea, + isInfiniteImageEmitMode, + configurationBehavior as ScaleChangeConfigurationBehavior); + break; + case ConfigurationBehaviorType.AlphaChange: + behaviorFactory = CreateAlphaChangeBehaviorFactory( + screensaverArea, + isInfiniteImageEmitMode, + configurationBehavior as AlphaChangeConfigurationBehavior); + break; + case ConfigurationBehaviorType.RotationChange: + behaviorFactory = CreateRotationChangeBehaviorFactory( + screensaverArea, + isInfiniteImageEmitMode, + configurationBehavior as RotationChangeConfigurationBehavior, + random); + break; + default: + throw new InvalidOperationException("No method defined for converting the " + + $"specified behavior type ({behaviorType}) to a behavior factory."); + } + + return behaviorFactory; + } + + private static Func CreateColorChangeBehaviorFactory( + ScreensaverArea screensaverArea, + bool isInfiniteImageEmitMode, + ColorChangeConfigurationBehavior configurationBehavior) + { + Func colorChangeBehaviorFactory; + var startColor = new Color( + configurationBehavior.StartColor.R, + configurationBehavior.StartColor.G, + configurationBehavior.StartColor.B, + byte.MaxValue); + var endColor = new Color( + configurationBehavior.EndColor.R, + configurationBehavior.EndColor.G, + configurationBehavior.EndColor.B, + byte.MaxValue); + if (!configurationBehavior.EndTransitionEnabled || !isInfiniteImageEmitMode) + { + colorChangeBehaviorFactory = () => new ColorChangeScreensaverImageItemBehavior( + screensaverArea, + startColor, + endColor, + configurationBehavior.TransitionTime); + } + else + { + var endTransitionColor = new Color( + configurationBehavior.EndTransitionColor.R, + configurationBehavior.EndTransitionColor.G, + configurationBehavior.EndTransitionColor.B, + byte.MaxValue); + colorChangeBehaviorFactory = () => new ColorChangeScreensaverImageItemBehavior( + screensaverArea, + startColor, + endColor, + configurationBehavior.TransitionTime, + endTransitionColor, + configurationBehavior.EndTransitionTime); + } + + return colorChangeBehaviorFactory; + } + + private static Func CreateScaleChangeBehaviorFactory( + ScreensaverArea screensaverArea, + bool isInfiniteImageEmitMode, + ScaleChangeConfigurationBehavior configurationBehavior) + { + Func scaleChangeBehaviorFactory; + if (!configurationBehavior.EndTransitionEnabled || !isInfiniteImageEmitMode) + { + scaleChangeBehaviorFactory = () => new ScaleChangeScreensaverImageItemBehavior( + screensaverArea, + new Vector2( + configurationBehavior.StartScaleX, + configurationBehavior.StartScaleY), + new Vector2(configurationBehavior.EndScaleX, configurationBehavior.EndScaleY), + configurationBehavior.TransitionTime); + } + else + { + scaleChangeBehaviorFactory = () => new ScaleChangeScreensaverImageItemBehavior( + screensaverArea, + new Vector2( + configurationBehavior.StartScaleX, + configurationBehavior.StartScaleY), + new Vector2(configurationBehavior.EndScaleX, configurationBehavior.EndScaleY), + configurationBehavior.TransitionTime, + new Vector2( + configurationBehavior.EndTransitionScaleX, + configurationBehavior.EndTransitionScaleY), + configurationBehavior.EndTransitionTime); + } + + return scaleChangeBehaviorFactory; + } + + private static Func CreateAlphaChangeBehaviorFactory( + ScreensaverArea screensaverArea, + bool isInfiniteImageEmitMode, + AlphaChangeConfigurationBehavior configurationBehavior) + { + Func alphaChangeBehaviorFactory; + if (!configurationBehavior.EndTransitionEnabled || !isInfiniteImageEmitMode) + { + alphaChangeBehaviorFactory = () => new AlphaChangeScreensaverImageItemBehavior( + screensaverArea, + configurationBehavior.StartAlpha, + configurationBehavior.EndAlpha, + configurationBehavior.TransitionTime); + } + else + { + alphaChangeBehaviorFactory = () => new AlphaChangeScreensaverImageItemBehavior( + screensaverArea, + configurationBehavior.StartAlpha, + configurationBehavior.EndAlpha, + configurationBehavior.TransitionTime, + configurationBehavior.EndTransitionAlpha, + configurationBehavior.EndTransitionTime); + } + + return alphaChangeBehaviorFactory; + } + + private static Func CreateRotationChangeBehaviorFactory( + ScreensaverArea screensaverArea, + bool isInfiniteImageEmitMode, + RotationChangeConfigurationBehavior configurationBehavior, + Random random) + { + Func rotationChangeBehaviorFactory; + var startRadians = MathHelper.ToRadians(configurationBehavior.StartRotation); + var endRadians = MathHelper.ToRadians(configurationBehavior.EndRotation); + if (!configurationBehavior.EndTransitionEnabled || !isInfiniteImageEmitMode) + { + rotationChangeBehaviorFactory = () => + { + if (configurationBehavior.RandomlyInvertRotation && random.Next(0, 2) == 0) + { + startRadians = -startRadians; + endRadians = -endRadians; + } + + return new RotationChangeScreensaverImageItemBehavior( + screensaverArea, + startRadians, + endRadians, + configurationBehavior.TransitionTime); + }; + } + else + { + var endTransitionRadians = + MathHelper.ToRadians(configurationBehavior.EndTransitionRotation); + rotationChangeBehaviorFactory = () => + { + if (configurationBehavior.RandomlyInvertRotation && random.Next(0, 2) == 0) + { + startRadians = -startRadians; + endRadians = -endRadians; + endTransitionRadians = -endTransitionRadians; + } + + return new RotationChangeScreensaverImageItemBehavior( + screensaverArea, + startRadians, + endRadians, + configurationBehavior.TransitionTime, + endTransitionRadians, + configurationBehavior.EndTransitionTime); + }; + } + + return rotationChangeBehaviorFactory; + } + } +} \ No newline at end of file diff --git a/src/EgamiFlowScreensaver/EnumHelper.cs b/src/EgamiFlowScreensaver/EnumHelper.cs new file mode 100644 index 0000000..fdeeeec --- /dev/null +++ b/src/EgamiFlowScreensaver/EnumHelper.cs @@ -0,0 +1,59 @@ +// +// Copyright (c) Adrian John Dunstan. All rights reserved. +// +// Licensed 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. +// + +namespace Natsnudasoft.EgamiFlowScreensaver +{ + using System.Linq; + using System.Reflection; + using Natsnudasoft.NatsnudaLibrary; + + /// + /// Provides a class containing helper operations that can be performed on enums or elements of + /// enums. + /// + public static class EnumHelper + { + /// + /// Gets a display name from the specified enum value, using an + /// if one exists. + /// + /// The enum type of the member that a display name is being retrieved + /// for. + /// The value of an enum that a display name is being retrieved for. + /// + /// A containing the display name that was retrieved. + /// + /// + public static string GetEnumDisplayName(T enumValue) + { + ParameterValidation.IsNotNull(enumValue, nameof(enumValue)); + + var enumValueMemberInfo = typeof(T).GetMember(enumValue.ToString()).FirstOrDefault(); + var enumDisplayName = enumValueMemberInfo?.ToString(); + if (enumValueMemberInfo != null) + { + var enumResourceDisplayNameAttribute = + enumValueMemberInfo.GetCustomAttribute(); + if (enumResourceDisplayNameAttribute != null) + { + enumDisplayName = enumResourceDisplayNameAttribute.DisplayName; + } + } + + return enumDisplayName; + } + } +} \ No newline at end of file diff --git a/src/EgamiFlowScreensaver/GameCompositionRoot.cs b/src/EgamiFlowScreensaver/GameCompositionRoot.cs index b35bd07..4c0947c 100644 --- a/src/EgamiFlowScreensaver/GameCompositionRoot.cs +++ b/src/EgamiFlowScreensaver/GameCompositionRoot.cs @@ -43,6 +43,9 @@ public static void Compose(GameServiceContainer gameServiceContainer) { ParameterValidation.IsNotNull(gameServiceContainer, nameof(gameServiceContainer)); + var random = new Random(); + gameServiceContainer.AddService(random); + var graphicsDeviceService = gameServiceContainer.GetService(); var nativeMethods = new NativeMethods(); @@ -68,6 +71,13 @@ public static void Compose(GameServiceContainer gameServiceContainer) var imageScaleService = new ImageScaleService(); gameServiceContainer.AddService(imageScaleService); + + var imageEmitDetailsFactory = + new ScreensaverConfigurationImageEmitDetailsFactory(gameServiceContainer, random); + gameServiceContainer.AddService(imageEmitDetailsFactory); + + var behaviorFactoriesFactory = new EnabledBehaviorFactoriesFactory(random); + gameServiceContainer.AddService(behaviorFactoriesFactory); } } } \ No newline at end of file diff --git a/src/EgamiFlowScreensaver/IBehaviorFactoriesFactory.cs b/src/EgamiFlowScreensaver/IBehaviorFactoriesFactory.cs new file mode 100644 index 0000000..dbe0d7d --- /dev/null +++ b/src/EgamiFlowScreensaver/IBehaviorFactoriesFactory.cs @@ -0,0 +1,44 @@ +// +// Copyright (c) Adrian John Dunstan. All rights reserved. +// +// Licensed 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. +// + +namespace Natsnudasoft.EgamiFlowScreensaver +{ + using System; + using System.Collections.Generic; + using Natsnudasoft.EgamiFlowScreensaver.Config; + + /// + /// Provides an interface describing operations to create factories that are capable of creating + /// behaviours that can be applied to images that are emitted by a screensaver. + /// + public interface IBehaviorFactoriesFactory + { + /// + /// Creates a collection of factories capable of creating behaviours that can be applied to + /// images emitted by a screensaver based on the specified list of behaviour configurations. + /// + /// The description of the area of the screensaver. + /// The list of configuration behaviours to use as a + /// template to create the behaviour factories. + /// A value indicating whether or not images will be + /// emitted infinitely. + /// The collection of factories that was created. + IEnumerable> Create( + ScreensaverArea screensaverArea, + IEnumerable configurationBehaviors, + bool isInfiniteImageEmitMode); + } +} \ No newline at end of file diff --git a/src/EgamiFlowScreensaver/IImageEmitDetails.cs b/src/EgamiFlowScreensaver/IImageEmitDetails.cs new file mode 100644 index 0000000..387c1ec --- /dev/null +++ b/src/EgamiFlowScreensaver/IImageEmitDetails.cs @@ -0,0 +1,76 @@ +// +// Copyright (c) Adrian John Dunstan. All rights reserved. +// +// Licensed 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. +// + +namespace Natsnudasoft.EgamiFlowScreensaver +{ + using System; + using System.Collections.Generic; + using Microsoft.Xna.Framework.Graphics; + + /// + /// Provides an interface describing the capability of creating image items for a + /// . + /// + /// + public interface IImageEmitDetails + { + /// + /// Gets the rate that images should be emitted by a . + /// + float ImageEmitRate { get; } + + /// + /// Gets the maximum number of images that should be emitted by a + /// . + /// + int MaxImageEmitCount { get; } + + /// + /// Gets a value indicating whether or not images will be emitted infinitely. + /// + /// if images should be emitted infinitely; otherwise + /// . + bool IsInfiniteImageEmitMode { get; } + + /// + /// Gets the time that emitted images should live for. + /// + /// This setting is only used if is + /// . + TimeSpan ImageEmitLifetime { get; } + + /// + /// Gets a list of factories that define how to create behaviours that will be attached to + /// any images emitted by a . + /// + IEnumerable> BehaviorFactories { get; } + + /// + /// Creates a based on the parameters of this + /// . + /// + /// The texture details describing the parameters of the image that an + /// emit location is being created for. + /// The that was created. + ScreensaverImageItem CreateScreensaverImageItem(Texture2D texture); + + /// + /// Inserts a set of default behaviour factories that define which behaviours will be + /// attached to any images emitted by a . + /// + void InsertDefaultBehaviorFactories(); + } +} \ No newline at end of file diff --git a/src/EgamiFlowScreensaver/IImageEmitDetailsFactory.cs b/src/EgamiFlowScreensaver/IImageEmitDetailsFactory.cs new file mode 100644 index 0000000..7a61a26 --- /dev/null +++ b/src/EgamiFlowScreensaver/IImageEmitDetailsFactory.cs @@ -0,0 +1,32 @@ +// +// Copyright (c) Adrian John Dunstan. All rights reserved. +// +// Licensed 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. +// + +namespace Natsnudasoft.EgamiFlowScreensaver +{ + /// + /// Provides an interface describing methods to create instances of + /// . + /// + public interface IImageEmitDetailsFactory + { + /// + /// Creates an instance of . + /// + /// The description of the area of the screensaver. + /// The created instance of . + IImageEmitDetails Create(ScreensaverArea screensaverArea); + } +} \ No newline at end of file diff --git a/src/EgamiFlowScreensaver/IScreensaverImageItemBehavior.cs b/src/EgamiFlowScreensaver/IScreensaverImageItemBehavior.cs new file mode 100644 index 0000000..42f4188 --- /dev/null +++ b/src/EgamiFlowScreensaver/IScreensaverImageItemBehavior.cs @@ -0,0 +1,59 @@ +// +// Copyright (c) Adrian John Dunstan. All rights reserved. +// +// Licensed 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. +// + +namespace Natsnudasoft.EgamiFlowScreensaver +{ + using Microsoft.Xna.Framework; + + /// + /// Provides an interface that encapsulates methods available to behaviours that can be composed + /// and applied to a . + /// + public interface IScreensaverImageItemBehavior + { + /// + /// Gets a value indicating whether this behaviour is finished. + /// + bool IsFinished { get; } + + /// + /// Gets a value indicating whether or not this behaviour will block the image item that + /// it is attached to from being destroyed until is set to + /// . + /// + /// if this behaviour should block the image item that it is + /// attached to from being destroyed; otherwise . + bool BlocksDestroy { get; } + + /// + /// Performs any initialization steps required by this + /// that will be applied to the specified + /// . + /// + /// The that is having + /// this behaviour initialized. + void Initialize(ScreensaverImageItem screensaverImageItem); + + /// + /// Performs any update steps required by this + /// that will be applied to the specified . + /// + /// The that is having + /// this behaviour updated. + /// A snapshot of the current game time. + void Update(ScreensaverImageItem screensaverImageItem, GameTime gameTime); + } +} \ No newline at end of file diff --git a/src/EgamiFlowScreensaver/ImageBackgroundDrawable.cs b/src/EgamiFlowScreensaver/ImageBackgroundDrawable.cs index 052a743..9a5f58d 100644 --- a/src/EgamiFlowScreensaver/ImageBackgroundDrawable.cs +++ b/src/EgamiFlowScreensaver/ImageBackgroundDrawable.cs @@ -21,6 +21,7 @@ namespace Natsnudasoft.EgamiFlowScreensaver using Microsoft.Xna.Framework.Graphics; using Natsnudasoft.NatsnudaLibrary; using SystemBitmap = System.Drawing.Bitmap; + using SystemColor = System.Drawing.Color; /// /// Provides a class which will draw a specified image as a @@ -34,6 +35,7 @@ public sealed class ImageBackgroundDrawable : BackgroundDrawable, IDisposable private readonly ITextureConverterService textureConverterService; private readonly IImageScaleService imageScaleService; private readonly ImageScaleMode imageScaleMode; + private readonly Color backgroundColor; private TiledTexture2D imageTiledTexture; private Vector2 imagePosition; @@ -46,6 +48,7 @@ public sealed class ImageBackgroundDrawable : BackgroundDrawable, IDisposable /// The image scale service. /// How to scale and position the background image. /// The description of the area of the screensaver. + /// The colour to use for the background. /// , /// , , or /// is . @@ -56,7 +59,8 @@ public sealed class ImageBackgroundDrawable : BackgroundDrawable, IDisposable ITextureConverterService textureConverterService, IImageScaleService imageScaleService, ImageScaleMode imageScaleMode, - ScreensaverArea screensaverArea) + ScreensaverArea screensaverArea, + SystemColor backgroundColor) : base(screensaverArea) { ParameterValidation.IsNotNull(backgroundImageFilePath, nameof(backgroundImageFilePath)); @@ -70,6 +74,11 @@ public sealed class ImageBackgroundDrawable : BackgroundDrawable, IDisposable this.textureConverterService = textureConverterService; this.imageScaleService = imageScaleService; this.imageScaleMode = imageScaleMode; + this.backgroundColor = new Color( + backgroundColor.R, + backgroundColor.G, + backgroundColor.B, + byte.MaxValue); } /// @@ -96,6 +105,16 @@ public override void LoadContent(GraphicsDevice graphicsDevice) } } + /// + /// is + /// . + public override void BeforeDraw(GraphicsDevice graphicsDevice) + { + ParameterValidation.IsNotNull(graphicsDevice, nameof(graphicsDevice)); + + graphicsDevice.Clear(this.backgroundColor); + } + /// /// is /// . diff --git a/src/EgamiFlowScreensaver/ImageEmitDetails.cs b/src/EgamiFlowScreensaver/ImageEmitDetails.cs new file mode 100644 index 0000000..f77b6e0 --- /dev/null +++ b/src/EgamiFlowScreensaver/ImageEmitDetails.cs @@ -0,0 +1,223 @@ +// +// Copyright (c) Adrian John Dunstan. All rights reserved. +// +// Licensed 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. +// + +namespace Natsnudasoft.EgamiFlowScreensaver +{ + using System; + using System.Collections.Generic; + using Microsoft.Xna.Framework; + using Microsoft.Xna.Framework.Graphics; + using Natsnudasoft.EgamiFlowScreensaver.Config; + using Natsnudasoft.NatsnudaLibrary; + + /// + /// Provides an abstract base class describing the capability of creating image items for a + /// . + /// + /// + public abstract class ImageEmitDetails : IImageEmitDetails + { + /// + /// Represents the possible range of the position in one direction that an image should be + /// emitted at by a . + /// + protected const int PositionDistribution = 5; + + /// + /// Represents the possible range of the position in both directions that an image should be + /// emitted at by a . + /// + protected const int TwoPositionDistribution = PositionDistribution * 2; + + private readonly List> behaviorFactories; + + /// + /// Initializes a new instance of the class. + /// + /// The description of the area of the screensaver. + /// The + /// representing the configured state of the screensaver. + /// A collection of factories that define how to create + /// behaviours that will be attached to any images emitted by a + /// . + /// , + /// , or + /// is . + protected ImageEmitDetails( + ScreensaverArea screensaverArea, + ScreensaverConfiguration screensaverConfiguration, + IEnumerable> behaviorFactories) + : this(screensaverArea, screensaverConfiguration, behaviorFactories, new Random()) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The description of the area of the screensaver. + /// The + /// representing the configured state of the screensaver. + /// A collection of factories that define how to create + /// behaviours that will be attached to any images emitted by a + /// . + /// A pseudo-random number generator that can be used to generate + /// randomness in the . + /// , + /// , , or + /// is . + protected ImageEmitDetails( + ScreensaverArea screensaverArea, + ScreensaverConfiguration screensaverConfiguration, + IEnumerable> behaviorFactories, + Random random) + { + ParameterValidation.IsNotNull(screensaverArea, nameof(screensaverArea)); + ParameterValidation + .IsNotNull(screensaverConfiguration, nameof(screensaverConfiguration)); + ParameterValidation.IsNotNull(behaviorFactories, nameof(behaviorFactories)); + ParameterValidation.IsNotNull(random, nameof(random)); + + this.ScreensaverArea = screensaverArea; + this.ScreensaverConfiguration = screensaverConfiguration; + this.Random = random; + this.behaviorFactories = + new List>(behaviorFactories); + } + + /// + public float ImageEmitRate + { + get => this.ScreensaverConfiguration.ImageEmitRate; + } + + /// + public int MaxImageEmitCount + { + get => this.ScreensaverConfiguration.MaxImageEmitCount; + } + + /// + public bool IsInfiniteImageEmitMode + { + get => this.ScreensaverConfiguration.IsInfiniteImageEmitMode; + } + + /// + public TimeSpan ImageEmitLifetime + { + get => this.ScreensaverConfiguration.ImageEmitLifetime; + } + + /// + public IEnumerable> BehaviorFactories + { + get => this.behaviorFactories; + } + + /// + /// Gets a value describing the area of the screensaver. + /// + protected ScreensaverArea ScreensaverArea { get; } + + /// + /// Gets the representing the configured + /// state of the screensaver. + /// + protected ScreensaverConfiguration ScreensaverConfiguration { get; } + + /// + /// Gets a pseudo-random number generator that can be used to generate randomness in the + /// . + /// + protected Random Random { get; } + + /// + public abstract ScreensaverImageItem CreateScreensaverImageItem(Texture2D texture); + + /// + public virtual void InsertDefaultBehaviorFactories() + { + this.behaviorFactories.InsertRange(0, this.CreateDefaultImageEmitBehaviorFactories()); + } + + /// + /// Creates a default moving behaviour factory that will be applied to this + /// . + /// + /// The moving behaviour factory that was created. + protected virtual Func CreateDefaultMovingBehaviorFactory() + { + IScreensaverImageItemBehavior CreateDefaultMovingBehavior() + { + const float minSpeed = MovingScreensaverImageItemBehavior.DefaultMinSpeed; + const float maxSpeed = MovingScreensaverImageItemBehavior.DefaultMaxSpeed; + var speed = new Vector2( + this.Random.NextFloat(minSpeed, maxSpeed), + this.Random.NextFloat(minSpeed, maxSpeed)); + this.RandomlyNegateSpeed(ref speed); + return new MovingScreensaverImageItemBehavior(this.ScreensaverArea, speed); + } + + return CreateDefaultMovingBehavior; + } + + /// + /// Randomly negates the X and Y components of a speed . + /// + /// The speed value that will have its' X and Y components randomly + /// negated. + [System.Diagnostics.CodeAnalysis.SuppressMessage( + "Microsoft.Design", + "CA1045:DoNotPassTypesByReference", + MessageId = "0#", + Justification = "We allow this here for performance.")] + protected void RandomlyNegateSpeed(ref Vector2 speed) + { + if (this.Random.Next(0, 2) == 0) + { + speed.X = -speed.X; + } + + if (this.Random.Next(0, 2) == 0) + { + speed.Y = -speed.Y; + } + } + + private Func CreateDefaultLifetimeBehaviorFactory() + { + IScreensaverImageItemBehavior CreateDefaultLifetimeBehavior() + { + return new LifetimeScreensaverImageItemBehavior( + this.ScreensaverArea, + this.ImageEmitLifetime); + } + + return CreateDefaultLifetimeBehavior; + } + + private IEnumerable> + CreateDefaultImageEmitBehaviorFactories() + { + if (this.ScreensaverConfiguration.IsInfiniteImageEmitMode) + { + yield return this.CreateDefaultLifetimeBehaviorFactory(); + } + + yield return this.CreateDefaultMovingBehaviorFactory(); + } + } +} \ No newline at end of file diff --git a/src/EgamiFlowScreensaver/ImageEmitLocation.cs b/src/EgamiFlowScreensaver/ImageEmitLocation.cs index 068fd39..9350984 100644 --- a/src/EgamiFlowScreensaver/ImageEmitLocation.cs +++ b/src/EgamiFlowScreensaver/ImageEmitLocation.cs @@ -63,6 +63,12 @@ public enum ImageEmitLocation /// Images should be emitted from random locations on the primary screen. /// [EnumResourceDisplayName(typeof(Resources), "ImageEmitLocationRandom")] - Random + Random, + + /// + /// Images should be emitted from a custom location. + /// + [EnumResourceDisplayName(typeof(Resources), "ImageEmitLocationCustom")] + Custom } } \ No newline at end of file diff --git a/src/EgamiFlowScreensaver/ImageScaleService.cs b/src/EgamiFlowScreensaver/ImageScaleService.cs index 8a3c274..a4c8cdc 100644 --- a/src/EgamiFlowScreensaver/ImageScaleService.cs +++ b/src/EgamiFlowScreensaver/ImageScaleService.cs @@ -84,7 +84,7 @@ private static Bitmap StretchImage(Image image, Size scaleSize) private static Bitmap TileImage(Image image, Size scaleSize) { var tiledBitmap = - new Bitmap(scaleSize.Width, scaleSize.Height, PixelFormat.Format32bppPArgb); + new Bitmap(scaleSize.Width, scaleSize.Height, PixelFormat.Format32bppArgb); try { tiledBitmap.SetResolution(image.HorizontalResolution, image.VerticalResolution); @@ -107,8 +107,8 @@ private static Bitmap TileImage(Image image, Size scaleSize) private static Bitmap ResizeImage(Image image, Size scaleSize) { var destRect = new Rectangle(0, 0, scaleSize.Width, scaleSize.Height); - var resizedImage = - new Bitmap(scaleSize.Width, scaleSize.Height, PixelFormat.Format32bppPArgb); + var resizedImage = new Bitmap( + scaleSize.Width, scaleSize.Height, PixelFormat.Format32bppArgb); try { resizedImage.SetResolution(image.HorizontalResolution, image.VerticalResolution); diff --git a/src/EgamiFlowScreensaver/LifetimeScreensaverImageItemBehavior.cs b/src/EgamiFlowScreensaver/LifetimeScreensaverImageItemBehavior.cs new file mode 100644 index 0000000..7dbb7fe --- /dev/null +++ b/src/EgamiFlowScreensaver/LifetimeScreensaverImageItemBehavior.cs @@ -0,0 +1,77 @@ +// +// Copyright (c) Adrian John Dunstan. All rights reserved. +// +// Licensed 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. +// + +namespace Natsnudasoft.EgamiFlowScreensaver +{ + using System; + using Microsoft.Xna.Framework; + using Natsnudasoft.NatsnudaLibrary; + + /// + /// Provides a behaviour to apply to an image item that causes the image item to be destroyed + /// after a specified lifetime. + /// + public sealed class LifetimeScreensaverImageItemBehavior : ScreensaverImageItemBehavior + { + private readonly TimeSpan lifetime; + private TimeSpan currentTime; + + /// + /// Initializes a new instance of the + /// class. + /// + /// The description of the area of the screensaver. + /// The time the image item this behaviour is applied to will live + /// for. + /// is + /// . + /// is less than a + /// zero time. + public LifetimeScreensaverImageItemBehavior( + ScreensaverArea screensaverArea, + TimeSpan lifetime) + : base(screensaverArea) + { + ParameterValidation.IsGreaterThanOrEqualTo(lifetime, TimeSpan.Zero, nameof(lifetime)); + + this.lifetime = lifetime; + } + + /// + /// is + /// . + public override void Initialize(ScreensaverImageItem screensaverImageItem) + { + ParameterValidation.IsNotNull(screensaverImageItem, nameof(screensaverImageItem)); + } + + /// + /// , or + /// is . + public override void Update(ScreensaverImageItem screensaverImageItem, GameTime gameTime) + { + ParameterValidation.IsNotNull(screensaverImageItem, nameof(screensaverImageItem)); + ParameterValidation.IsNotNull(gameTime, nameof(gameTime)); + + this.currentTime += gameTime.ElapsedGameTime; + if (this.currentTime >= this.lifetime) + { + screensaverImageItem.BeginDestroy(); + this.IsFinished = true; + } + } + } +} \ No newline at end of file diff --git a/src/EgamiFlowScreensaver/MovingScreensaverImageItemBehavior.cs b/src/EgamiFlowScreensaver/MovingScreensaverImageItemBehavior.cs new file mode 100644 index 0000000..0d3ac05 --- /dev/null +++ b/src/EgamiFlowScreensaver/MovingScreensaverImageItemBehavior.cs @@ -0,0 +1,103 @@ +// +// Copyright (c) Adrian John Dunstan. All rights reserved. +// +// Licensed 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. +// + +namespace Natsnudasoft.EgamiFlowScreensaver +{ + using System; + using Microsoft.Xna.Framework; + using Natsnudasoft.NatsnudaLibrary; + + /// + /// Provides a behaviour to apply to an image item that causes the image item to move around the + /// screen at a specified speed. + /// + /// + public class MovingScreensaverImageItemBehavior : ScreensaverImageItemBehavior + { + /// + /// Represents the default minimum speed that an image should be emitted at by a . + /// + public const float DefaultMinSpeed = 0.5f; + + /// + /// Represents the default maximum speed that an image should be emitted at by a . + /// + public const float DefaultMaxSpeed = 8f; + + private readonly Vector2 magnitude; + private Vector2 direction; + + /// + /// Initializes a new instance of the + /// class. + /// + /// The description of the area of the screensaver. + /// The speed at which the image item this behaviour is applied to will + /// move at. + /// is + /// . + public MovingScreensaverImageItemBehavior(ScreensaverArea screensaverArea, Vector2 speed) + : base(screensaverArea) + { + this.direction = new Vector2(Math.Sign(speed.X), Math.Sign(speed.Y)); + this.magnitude = new Vector2(Math.Abs(speed.X), Math.Abs(speed.Y)); + } + + /// + /// is + /// . + public override void Initialize(ScreensaverImageItem screensaverImageItem) + { + ParameterValidation.IsNotNull(screensaverImageItem, nameof(screensaverImageItem)); + } + + /// + /// is + /// . + public override void Update(ScreensaverImageItem screensaverImageItem, GameTime gameTime) + { + ParameterValidation.IsNotNull(screensaverImageItem, nameof(screensaverImageItem)); + + var speed = this.magnitude * this.direction; + screensaverImageItem.Position += speed; + var topLeftPosition = screensaverImageItem.Position - screensaverImageItem.Origin; + var width = screensaverImageItem.Texture.Width; + var height = screensaverImageItem.Texture.Height; + var screensaverBounds = this.ScreensaverArea.ScreensaverGameBounds; + if ((speed.X > 0 && topLeftPosition.X + width > screensaverBounds.Right) || + (speed.X < 0 && topLeftPosition.X < screensaverBounds.Left)) + { + this.direction = new Vector2(-this.direction.X, this.direction.Y); + var newX = speed.X > 0 ? screensaverBounds.Right - width : screensaverBounds.Left; + screensaverImageItem.Position = new Vector2( + newX + screensaverImageItem.Origin.X, + screensaverImageItem.Position.Y); + } + + if ((speed.Y > 0 && topLeftPosition.Y + height > screensaverBounds.Bottom) || + (speed.Y < 0 && topLeftPosition.Y < screensaverBounds.Top)) + { + this.direction = new Vector2(this.direction.X, -this.direction.Y); + var newY = speed.Y > 0 ? screensaverBounds.Bottom - height : screensaverBounds.Top; + screensaverImageItem.Position = new Vector2( + screensaverImageItem.Position.X, + newY + screensaverImageItem.Origin.Y); + } + } + } +} \ No newline at end of file diff --git a/src/EgamiFlowScreensaver/Program.cs b/src/EgamiFlowScreensaver/Program.cs index a397cc4..f7b58ed 100644 --- a/src/EgamiFlowScreensaver/Program.cs +++ b/src/EgamiFlowScreensaver/Program.cs @@ -106,9 +106,13 @@ private static DialogResult ShowConfig(IntPtr screensaverSettingsChildHandle) Application.EnableVisualStyles(); var configurationFileService = new ConfigurationFileService(); var configurationFilesTempCache = new ConfigurationFilesTempCache(); + var behaviorConfigurationFactory = new BehaviorConfigurationFactory(); + var behaviorConfigurationFormFactory = new BehaviorConfigurationFormFactory(); using (var viewModel = new ConfigurationFormViewModel( configurationFileService, - configurationFilesTempCache)) + configurationFilesTempCache, + behaviorConfigurationFactory, + behaviorConfigurationFormFactory)) { var nativeMethods = new NativeMethods(); if (screensaverSettingsChildHandle != IntPtr.Zero) diff --git a/src/EgamiFlowScreensaver/Properties/Resources.Designer.cs b/src/EgamiFlowScreensaver/Properties/Resources.Designer.cs index a1ca604..7858b1c 100644 --- a/src/EgamiFlowScreensaver/Properties/Resources.Designer.cs +++ b/src/EgamiFlowScreensaver/Properties/Resources.Designer.cs @@ -60,6 +60,15 @@ internal class Resources { } } + /// + /// Looks up a localized string similar to A behaviour that causes emitted images to transition from one alpha value (transparency) to another over a specified time frame. Valid alpha values are between 0.0 and 1.0 inclusive, although the alpha start value can be less than 0.0 to delay when the transition becomes apparent on screen.. + /// + internal static string AlphaChangeBehaviorDescription { + get { + return ResourceManager.GetString("AlphaChangeBehaviorDescription", resourceCulture); + } + } + /// /// Looks up a localized string similar to No background image selected.. /// @@ -78,6 +87,15 @@ internal class Resources { } } + /// + /// Looks up a localized string similar to A behaviour that causes emitted images to transition from one colour to another over a specified time frame.. + /// + internal static string ColorChangeBehaviorDescription { + get { + return ResourceManager.GetString("ColorChangeBehaviorDescription", resourceCulture); + } + } + /// /// Looks up a localized string similar to Configuration Directory Error. /// @@ -195,6 +213,15 @@ internal class Resources { } } + /// + /// Looks up a localized string similar to Custom.... + /// + internal static string ImageEmitLocationCustom { + get { + return ResourceManager.GetString("ImageEmitLocationCustom", resourceCulture); + } + } + /// /// Looks up a localized string similar to Random. /// @@ -240,6 +267,42 @@ internal class Resources { } } + /// + /// Looks up a localized string similar to Alpha Change Behaviour. + /// + internal static string ImageItemBehaviorTypeAlphaChange { + get { + return ResourceManager.GetString("ImageItemBehaviorTypeAlphaChange", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Colour Change Behaviour. + /// + internal static string ImageItemBehaviorTypeColorChange { + get { + return ResourceManager.GetString("ImageItemBehaviorTypeColorChange", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Rotation Change Behaviour. + /// + internal static string ImageItemBehaviorTypeRotationChange { + get { + return ResourceManager.GetString("ImageItemBehaviorTypeRotationChange", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Scale Change Behaviour. + /// + internal static string ImageItemBehaviorTypeScaleChange { + get { + return ResourceManager.GetString("ImageItemBehaviorTypeScaleChange", resourceCulture); + } + } + /// /// Looks up a localized string similar to Centre. /// @@ -285,6 +348,62 @@ internal class Resources { } } + /// + /// Looks up a localized string similar to No configuration options available.. + /// + internal static string NoConfigurationFormForBehaviorCaption { + get { + return ResourceManager.GetString("NoConfigurationFormForBehaviorCaption", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to There are no configuration options available for the selected behaviour type.. + /// + internal static string NoConfigurationFormForBehaviorText { + get { + return ResourceManager.GetString("NoConfigurationFormForBehaviorText", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to A behaviour that causes emitted images to transition from one rotation value (in degrees) to another over a specified time frame; valid rotation values can be any negative or positive value. + ///Please note that changing image rotation can cause incorrect collision behaviour when images bounce off of the edges of the screen.. + /// + internal static string RotationChangeBehaviorDescription { + get { + return ResourceManager.GetString("RotationChangeBehaviorDescription", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to A behaviour that causes emitted images to transition from one scale (relative size) to another over a specified time frame. Valid scale values are between 0.0 and 1.0 inclusive, although scale start values van be less than 0.0 to delay when the transition becomes apparent on screen. + ///Please note that changing image scale can cause incorrect collision behaviour when images bounce off of the edges of the screen.. + /// + internal static string ScaleChangeBehaviorDescription { + get { + return ResourceManager.GetString("ScaleChangeBehaviorDescription", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Transition time less than image lifetime.. + /// + internal static string TransitionTimeLessThanLifetimeCaption { + get { + return ResourceManager.GetString("TransitionTimeLessThanLifetimeCaption", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The transition time of this behaviour is longer than the lifetime of any emitted images, this may cause strange behaviour, are you sure you want to continue with these values?. + /// + internal static string TransitionTimeLessThanLifetimeText { + get { + return ResourceManager.GetString("TransitionTimeLessThanLifetimeText", resourceCulture); + } + } + /// /// Looks up a localized string similar to Unhandled Exception. /// diff --git a/src/EgamiFlowScreensaver/Properties/Resources.resx b/src/EgamiFlowScreensaver/Properties/Resources.resx index d4a7e00..2f65536 100644 --- a/src/EgamiFlowScreensaver/Properties/Resources.resx +++ b/src/EgamiFlowScreensaver/Properties/Resources.resx @@ -201,4 +201,45 @@ Image Files (*.bmp, *.jpg, *.jpeg, *.png) | *.bmp; *.jpg; *.jpeg; *.png|All Files|*.* + + Custom... + + + Colour Change Behaviour + + + Alpha Change Behaviour + + + Scale Change Behaviour + + + No configuration options available. + + + There are no configuration options available for the selected behaviour type. + + + Rotation Change Behaviour + + + A behaviour that causes emitted images to transition from one alpha value (transparency) to another over a specified time frame. Valid alpha values are between 0.0 and 1.0 inclusive, although the alpha start value can be less than 0.0 to delay when the transition becomes apparent on screen. + + + A behaviour that causes emitted images to transition from one colour to another over a specified time frame. + + + A behaviour that causes emitted images to transition from one rotation value (in degrees) to another over a specified time frame; valid rotation values can be any negative or positive value. +Please note that changing image rotation can cause incorrect collision behaviour when images bounce off of the edges of the screen. + + + A behaviour that causes emitted images to transition from one scale (relative size) to another over a specified time frame. Valid scale values are between 0.0 and 1.0 inclusive, although scale start values van be less than 0.0 to delay when the transition becomes apparent on screen. +Please note that changing image scale can cause incorrect collision behaviour when images bounce off of the edges of the screen. + + + Transition time less than image lifetime. + + + The transition time of this behaviour is longer than the lifetime of any emitted images, this may cause strange behaviour, are you sure you want to continue with these values? + \ No newline at end of file diff --git a/src/EgamiFlowScreensaver/RadioGroupBox.cs b/src/EgamiFlowScreensaver/RadioGroupBox.cs index 093e26a..481dd9a 100644 --- a/src/EgamiFlowScreensaver/RadioGroupBox.cs +++ b/src/EgamiFlowScreensaver/RadioGroupBox.cs @@ -18,6 +18,7 @@ namespace Natsnudasoft.EgamiFlowScreensaver { using System; using System.Collections.Generic; + using System.ComponentModel; using System.Windows.Forms; using Natsnudasoft.NatsnudaLibrary; @@ -27,7 +28,7 @@ namespace Natsnudasoft.EgamiFlowScreensaver /// the group. /// /// - public class RadioGroupBox : GroupBox + public class RadioGroupBox : GroupBox, INotifyPropertyChanged { private readonly List radioButtons; private int selectedRadioIndex; @@ -45,6 +46,9 @@ public RadioGroupBox() /// public event EventHandler SelectedRadioIndexChanged; + /// + public event PropertyChangedEventHandler PropertyChanged; + /// /// Gets or sets the index of the that is currently selected in /// this . @@ -61,6 +65,8 @@ public int SelectedRadioIndex this.radioButtons[value].Checked = true; this.selectedRadioIndex = value; this.OnSelectedRadioIndexChanged(); + this.OnPropertyChanged(nameof(this.SelectedRadioIndex)); + this.OnPropertyChanged(nameof(this.SelectedRadioValue)); } } } @@ -94,6 +100,15 @@ protected override void OnControlAdded(ControlEventArgs e) } } + /// + /// Called when the value of a property changes. + /// + /// The name of the property that changed. + protected virtual void OnPropertyChanged(string propertyName) + { + this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); + } + private void RadioButtonCheckedChanged(RadioButton radioButton, int newIndex) { if (radioButton.Checked) diff --git a/src/EgamiFlowScreensaver/RandomCornerImageEmitDetails.cs b/src/EgamiFlowScreensaver/RandomCornerImageEmitDetails.cs new file mode 100644 index 0000000..2d52e46 --- /dev/null +++ b/src/EgamiFlowScreensaver/RandomCornerImageEmitDetails.cs @@ -0,0 +1,151 @@ +// +// Copyright (c) Adrian John Dunstan. All rights reserved. +// +// Licensed 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. +// + +namespace Natsnudasoft.EgamiFlowScreensaver +{ + using System; + using System.Collections.Generic; + using Microsoft.Xna.Framework.Graphics; + using Natsnudasoft.EgamiFlowScreensaver.Config; + using Natsnudasoft.NatsnudaLibrary; + + /// + /// Provides a class capable of creating locations in a random corner of the screen for a + /// . + /// + public sealed class RandomCornerImageEmitDetails : ImageEmitDetails + { + private readonly ImageEmitDetails[] cornerEmitDetailsList; + + /// + /// Initializes a new instance of the class. + /// + /// The description of the area of the screensaver. + /// + /// The representing the configured state of the + /// screensaver. + /// A collection of factories that define how to create + /// behaviours that will be attached to any images emitted by a + /// . + /// , + /// , or + /// is . + public RandomCornerImageEmitDetails( + ScreensaverArea screensaverArea, + ScreensaverConfiguration screensaverConfiguration, + IEnumerable> behaviorFactories) + : base(screensaverArea, screensaverConfiguration, behaviorFactories) + { + var bottomRightImageEmitDetails = new BottomRightImageEmitDetails( + screensaverArea, + screensaverConfiguration, + behaviorFactories); + var topRightImageEmitDetails = new TopRightImageEmitDetails( + screensaverArea, + screensaverConfiguration, + behaviorFactories); + var topLeftImageEmitDetails = new TopLeftImageEmitDetails( + screensaverArea, + screensaverConfiguration, + behaviorFactories); + var bottomLeftImageEmitDetails = new BottomLeftImageEmitDetails( + screensaverArea, + screensaverConfiguration, + behaviorFactories); + this.cornerEmitDetailsList = new ImageEmitDetails[] + { + bottomRightImageEmitDetails, + topRightImageEmitDetails, + topLeftImageEmitDetails, + bottomLeftImageEmitDetails + }; + } + + /// + /// Initializes a new instance of the class. + /// + /// The description of the area of the screensaver. + /// The + /// representing the configured state of the screensaver. + /// A collection of factories that define how to create + /// behaviours that will be attached to any images emitted by a + /// . + /// A pseudo-random number generator that can be used to generate + /// randomness in the . + /// , + /// , , or + /// is . + public RandomCornerImageEmitDetails( + ScreensaverArea screensaverArea, + ScreensaverConfiguration screensaverConfiguration, + IEnumerable> behaviorFactories, + Random random) + : base(screensaverArea, screensaverConfiguration, behaviorFactories, random) + { + var bottomRightImageEmitDetails = new BottomRightImageEmitDetails( + screensaverArea, + screensaverConfiguration, + behaviorFactories, + random); + var topRightImageEmitDetails = new TopRightImageEmitDetails( + screensaverArea, + screensaverConfiguration, + behaviorFactories, + random); + var topLeftImageEmitDetails = new TopLeftImageEmitDetails( + screensaverArea, + screensaverConfiguration, + behaviorFactories, + random); + var bottomLeftImageEmitDetails = new BottomLeftImageEmitDetails( + screensaverArea, + screensaverConfiguration, + behaviorFactories, + random); + this.cornerEmitDetailsList = new ImageEmitDetails[] + { + bottomRightImageEmitDetails, + topRightImageEmitDetails, + topLeftImageEmitDetails, + bottomLeftImageEmitDetails + }; + } + + /// + public override void InsertDefaultBehaviorFactories() + { + foreach (var cornerEmitDetails in this.cornerEmitDetailsList) + { + cornerEmitDetails.InsertDefaultBehaviorFactories(); + } + + base.InsertDefaultBehaviorFactories(); + } + + /// + /// is + /// . + public override ScreensaverImageItem CreateScreensaverImageItem(Texture2D texture) + { + ParameterValidation.IsNotNull(texture, nameof(texture)); + + var randomEmitDetailFunctionIndex = + this.Random.Next(0, this.cornerEmitDetailsList.Length); + return this.cornerEmitDetailsList[randomEmitDetailFunctionIndex] + .CreateScreensaverImageItem(texture); + } + } +} \ No newline at end of file diff --git a/src/EgamiFlowScreensaver/RandomImageEmitDetails.cs b/src/EgamiFlowScreensaver/RandomImageEmitDetails.cs new file mode 100644 index 0000000..1ec4a88 --- /dev/null +++ b/src/EgamiFlowScreensaver/RandomImageEmitDetails.cs @@ -0,0 +1,100 @@ +// +// Copyright (c) Adrian John Dunstan. All rights reserved. +// +// Licensed 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. +// + +namespace Natsnudasoft.EgamiFlowScreensaver +{ + using System; + using System.Collections.Generic; + using System.Linq; + using Microsoft.Xna.Framework; + using Microsoft.Xna.Framework.Graphics; + using Natsnudasoft.EgamiFlowScreensaver.Config; + using Natsnudasoft.NatsnudaLibrary; + + /// + /// Provides a class capable of creating locations at a random position on a screen for a + /// . + /// + public sealed class RandomImageEmitDetails : ImageEmitDetails + { + /// + /// Initializes a new instance of the class. + /// + /// The description of the area of the screensaver. + /// The + /// representing the configured state of the screensaver. + /// A collection of factories that define how to create + /// behaviours that will be attached to any images emitted by a + /// . + /// , + /// , or + /// is . + public RandomImageEmitDetails( + ScreensaverArea screensaverArea, + ScreensaverConfiguration screensaverConfiguration, + IEnumerable> behaviorFactories) + : base(screensaverArea, screensaverConfiguration, behaviorFactories) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The description of the area of the screensaver. + /// The + /// representing the configured state of the screensaver. + /// A collection of factories that define how to create + /// behaviours that will be attached to any images emitted by a + /// . + /// A pseudo-random number generator that can be used to generate + /// randomness in the . + /// , + /// , , or + /// is . + public RandomImageEmitDetails( + ScreensaverArea screensaverArea, + ScreensaverConfiguration screensaverConfiguration, + IEnumerable> behaviorFactories, + Random random) + : base(screensaverArea, screensaverConfiguration, behaviorFactories, random) + { + } + + /// + /// is + /// . + public override ScreensaverImageItem CreateScreensaverImageItem(Texture2D texture) + { + ParameterValidation.IsNotNull(texture, nameof(texture)); + + var origin = new Vector2(texture.Width, texture.Height) / 2f; + var minX = this.ScreensaverArea.PrimaryGameBounds.Left - texture.Width; + var maxX = this.ScreensaverArea.PrimaryGameBounds.Right; + var minY = this.ScreensaverArea.PrimaryGameBounds.Top - texture.Height; + var maxY = this.ScreensaverArea.PrimaryGameBounds.Bottom; + var position = new Vector2( + this.Random.NextFloat(minX, maxX), + this.Random.NextFloat(minY, maxY)); + return new ScreensaverImageItem( + texture, + this.BehaviorFactories.Select(f => f?.Invoke())) + { + Position = position, + Origin = origin + }; + } + } +} \ No newline at end of file diff --git a/src/EgamiFlowScreensaver/RotationChangeScreensaverImageItemBehavior.cs b/src/EgamiFlowScreensaver/RotationChangeScreensaverImageItemBehavior.cs new file mode 100644 index 0000000..b6e694d --- /dev/null +++ b/src/EgamiFlowScreensaver/RotationChangeScreensaverImageItemBehavior.cs @@ -0,0 +1,96 @@ +// +// Copyright (c) Adrian John Dunstan. All rights reserved. +// +// Licensed 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. +// + +namespace Natsnudasoft.EgamiFlowScreensaver +{ + using System; + using Microsoft.Xna.Framework; + + /// + /// Provides a behaviour to apply to an image item that causes the image item to transition from + /// one rotation value to another over a specified time frame. + /// + /// + public class RotationChangeScreensaverImageItemBehavior : + TransitionScreensaverImageItemBehavior + { + /// + /// Initializes a new instance of the + /// class. + /// + /// The description of the area of the screensaver. + /// The rotation value that the image item this behaviour is + /// applied to will start at (in radians). + /// The rotation value that the image item this behaviour is + /// applied to will finish at (in radians). + /// The time the image item this behaviour is applied to will + /// take to transition between the specified rotation values. + /// is + /// . + /// is less + /// than a zero time. + public RotationChangeScreensaverImageItemBehavior( + ScreensaverArea screensaverArea, + float startRotation, + float endRotation, + TimeSpan transitionTime) + : base( + screensaverArea, + transitionTime, + (s, p) => s.Rotation = + MathHelper.WrapAngle(MathHelper.Lerp(startRotation, endRotation, p))) + { + } + + /// + /// Initializes a new instance of the + /// class. + /// + /// The description of the area of the screensaver. + /// The rotation value that the image item this behaviour is + /// applied to will start at (in radians). + /// The rotation value that the image item this behaviour is + /// applied to will finish at (in radians). + /// The time the image item this behaviour is applied to will + /// take to transition between the specified rotation values. + /// The rotation value that the image item this + /// behaviour is applied to will finish at when it is being destroyed (starting from + /// ). + /// The time the image item this behaviour is applied to + /// will take to transition when it is being destroyed. + /// is + /// . + /// , or + /// is less than a zero time. + public RotationChangeScreensaverImageItemBehavior( + ScreensaverArea screensaverArea, + float startRotation, + float endRotation, + TimeSpan transitionTime, + float endTransitionRotation, + TimeSpan endTransitionTime) + : base( + screensaverArea, + transitionTime, + (s, p) => s.Rotation = + MathHelper.WrapAngle(MathHelper.Lerp(startRotation, endRotation, p)), + endTransitionTime, + (s, p) => s.Rotation = + MathHelper.WrapAngle(MathHelper.Lerp(endRotation, endTransitionRotation, p))) + { + } + } +} \ No newline at end of file diff --git a/src/EgamiFlowScreensaver/ScaleChangeScreensaverImageItemBehavior.cs b/src/EgamiFlowScreensaver/ScaleChangeScreensaverImageItemBehavior.cs new file mode 100644 index 0000000..a8bdadf --- /dev/null +++ b/src/EgamiFlowScreensaver/ScaleChangeScreensaverImageItemBehavior.cs @@ -0,0 +1,100 @@ +// +// Copyright (c) Adrian John Dunstan. All rights reserved. +// +// Licensed 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. +// + +namespace Natsnudasoft.EgamiFlowScreensaver +{ + using System; + using Microsoft.Xna.Framework; + + /// + /// Provides a behaviour to apply to an image item that causes the image item to transition from + /// one scale to another over a specified time frame. + /// + /// + public sealed class ScaleChangeScreensaverImageItemBehavior : + TransitionScreensaverImageItemBehavior + { + /// + /// Initializes a new instance of the + /// class. + /// + /// The description of the area of the screensaver. + /// The scale that the image item this behaviour is applied to will + /// start at. This value can have components out of range to delay the start of the + /// transition. + /// The scale that the image item this behaviour is applied to will + /// finish at. This value can have components out of range to advance the end of the + /// transition. + /// The time the image item this behaviour is applied to will + /// take to transition between the specified scales. + /// is + /// . + /// is less + /// than a zero time. + public ScaleChangeScreensaverImageItemBehavior( + ScreensaverArea screensaverArea, + Vector2 startScale, + Vector2 endScale, + TimeSpan transitionTime) + : base( + screensaverArea, + transitionTime, + (s, p) => s.Scale = + Vector2.Max(Vector2.Lerp(startScale, endScale, p), Vector2.Zero)) + { + } + + /// + /// Initializes a new instance of the + /// class. + /// + /// The description of the area of the screensaver. + /// The scale that the image item this behaviour is applied to will + /// start at. This value can have components out of range to delay the start of the + /// transition. + /// The scale that the image item this behaviour is applied to will + /// finish at. This value can have components out of range to advance the end of the + /// transition. + /// The time the image item this behaviour is applied to will + /// take to transition between the specified scales. + /// The scale value that the image item this behaviour is + /// applied to will finish at when it is being destroyed (starting from + /// ). + /// The time the image item this behaviour is applied to + /// will take to transition when it is being destroyed. + /// is + /// . + /// , or + /// is less than a zero time. + public ScaleChangeScreensaverImageItemBehavior( + ScreensaverArea screensaverArea, + Vector2 startScale, + Vector2 endScale, + TimeSpan transitionTime, + Vector2 endTransitionScale, + TimeSpan endTransitionTime) + : base( + screensaverArea, + transitionTime, + (s, p) => s.Scale = + Vector2.Max(Vector2.Lerp(startScale, endScale, p), Vector2.Zero), + endTransitionTime, + (s, p) => s.Scale = + Vector2.Max(Vector2.Lerp(endScale, endTransitionScale, p), Vector2.Zero)) + { + } + } +} \ No newline at end of file diff --git a/src/EgamiFlowScreensaver/ScreensaverConfigurationBackgroundDrawableFactory.cs b/src/EgamiFlowScreensaver/ScreensaverConfigurationBackgroundDrawableFactory.cs index 5660d7b..bc2570a 100644 --- a/src/EgamiFlowScreensaver/ScreensaverConfigurationBackgroundDrawableFactory.cs +++ b/src/EgamiFlowScreensaver/ScreensaverConfigurationBackgroundDrawableFactory.cs @@ -114,7 +114,8 @@ public BackgroundDrawable Create(ScreensaverArea screensaverArea) textureConverterService, imageScaleService, screensaverConfiguration.BackgroundImageScaleMode, - screensaverArea); + screensaverArea, + screensaverConfiguration.BackgroundColor); } private static BackgroundDrawable CreateSolidColorBackgroundDrawable( diff --git a/src/EgamiFlowScreensaver/ScreensaverConfigurationImageEmitDetailsFactory.cs b/src/EgamiFlowScreensaver/ScreensaverConfigurationImageEmitDetailsFactory.cs new file mode 100644 index 0000000..1f4b653 --- /dev/null +++ b/src/EgamiFlowScreensaver/ScreensaverConfigurationImageEmitDetailsFactory.cs @@ -0,0 +1,150 @@ +// +// Copyright (c) Adrian John Dunstan. All rights reserved. +// +// Licensed 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. +// + +namespace Natsnudasoft.EgamiFlowScreensaver +{ + using System; + using Microsoft.Xna.Framework; + using Natsnudasoft.EgamiFlowScreensaver.Config; + using Natsnudasoft.NatsnudaLibrary; + + /// + /// Provides a class which will create instances of based on a + /// retrieved from the specified + /// . + /// + /// + /// + public class ScreensaverConfigurationImageEmitDetailsFactory : IImageEmitDetailsFactory + { + private readonly IServiceProvider serviceProvider; + private readonly Random random; + + /// + /// Initializes a new instance of the + /// class. + /// + /// The service provider for the currently running + /// . + /// is + /// . + public ScreensaverConfigurationImageEmitDetailsFactory(IServiceProvider serviceProvider) + : this(serviceProvider, new Random()) + { + } + + /// + /// Initializes a new instance of the + /// class. + /// + /// The service provider for the currently running + /// . + /// A pseudo-random number generator that can be used to generate + /// randomness in the generated by this factory. + /// , or + /// is . + public ScreensaverConfigurationImageEmitDetailsFactory( + IServiceProvider serviceProvider, + Random random) + { + ParameterValidation.IsNotNull(serviceProvider, nameof(serviceProvider)); + ParameterValidation.IsNotNull(random, nameof(random)); + + this.serviceProvider = serviceProvider; + this.random = random; + } + + /// + /// is + /// . + public IImageEmitDetails Create(ScreensaverArea screensaverArea) + { + ParameterValidation.IsNotNull(screensaverArea, nameof(screensaverArea)); + + var configFileService = this.serviceProvider.GetService(); + var screensaverConfiguration = configFileService.Open(); + var behaviorFactoriesFactory = this.serviceProvider + .GetService(); + var behaviorFactories = behaviorFactoriesFactory.Create( + screensaverArea, + screensaverConfiguration.Behaviors, + screensaverConfiguration.IsInfiniteImageEmitMode); + IImageEmitDetails imageEmitDetails; + switch (screensaverConfiguration.ImageEmitLocation) + { + case ImageEmitLocation.Custom: + imageEmitDetails = new CustomImageEmitDetails( + screensaverArea, + screensaverConfiguration, + behaviorFactories, + this.random); + break; + case ImageEmitLocation.RandomCorner: + imageEmitDetails = new RandomCornerImageEmitDetails( + screensaverArea, + screensaverConfiguration, + behaviorFactories, + this.random); + break; + case ImageEmitLocation.Random: + imageEmitDetails = new RandomImageEmitDetails( + screensaverArea, + screensaverConfiguration, + behaviorFactories, + this.random); + break; + case ImageEmitLocation.Center: + imageEmitDetails = new CenterImageEmitDetails( + screensaverArea, + screensaverConfiguration, + behaviorFactories, + this.random); + break; + case ImageEmitLocation.BottomRight: + imageEmitDetails = new BottomRightImageEmitDetails( + screensaverArea, + screensaverConfiguration, + behaviorFactories, + this.random); + break; + case ImageEmitLocation.TopRight: + imageEmitDetails = new TopRightImageEmitDetails( + screensaverArea, + screensaverConfiguration, + behaviorFactories, + this.random); + break; + case ImageEmitLocation.TopLeft: + imageEmitDetails = new TopLeftImageEmitDetails( + screensaverArea, + screensaverConfiguration, + behaviorFactories, + this.random); + break; + case ImageEmitLocation.BottomLeft: + default: + imageEmitDetails = new BottomLeftImageEmitDetails( + screensaverArea, + screensaverConfiguration, + behaviorFactories, + this.random); + break; + } + + return imageEmitDetails; + } + } +} \ No newline at end of file diff --git a/src/EgamiFlowScreensaver/ScreensaverGame.cs b/src/EgamiFlowScreensaver/ScreensaverGame.cs index aad8a26..97f1dad 100644 --- a/src/EgamiFlowScreensaver/ScreensaverGame.cs +++ b/src/EgamiFlowScreensaver/ScreensaverGame.cs @@ -105,13 +105,16 @@ protected override void Initialize() this.backgroundDrawableManager.Initialize(this.screensaverArea); this.screensaverImageManager = new ScreensaverImageManager(this.screensaverArea); this.InitializeImages(); + var random = this.Services.GetService(); + var imageEmitDetailsFactory = this.Services.GetService(); + var imageEmitDetails = imageEmitDetailsFactory.Create(this.screensaverArea); + imageEmitDetails.InsertDefaultBehaviorFactories(); this.screensaverImageEmitter = new ScreensaverImageEmitter( - new ScreensaverImageEmitterCounter(this.screensaverConfiguration.ImageEmitRate), - this.screensaverArea, + new ScreensaverImageEmitterCounter(imageEmitDetails.ImageEmitRate), this.screensaverImageManager, this.screensaverTextures, - this.screensaverConfiguration.MaxImageEmitCount, - this.screensaverConfiguration.ImageEmitLocation); + imageEmitDetails, + random); this.screensaverImageEmitter.Start(); base.Initialize(); } @@ -151,7 +154,7 @@ protected override void Update(GameTime gameTime) this.Exit(); } #endif - this.screensaverImageManager.Update(); + this.screensaverImageManager.Update(gameTime); this.screensaverImageEmitter.Update(gameTime); #if !DEBUG diff --git a/src/EgamiFlowScreensaver/ScreensaverImageEmitter.cs b/src/EgamiFlowScreensaver/ScreensaverImageEmitter.cs index 79d0651..956a08d 100644 --- a/src/EgamiFlowScreensaver/ScreensaverImageEmitter.cs +++ b/src/EgamiFlowScreensaver/ScreensaverImageEmitter.cs @@ -32,51 +32,73 @@ public sealed class ScreensaverImageEmitter private const int PositionDistribution = 5; private const int TwoPositionDistribution = PositionDistribution * 2; - private readonly ScreensaverArea screensaverArea; private readonly ScreensaverImageEmitterCounter counter; private readonly ScreensaverImageManager screensaverImageManager; private readonly IReadOnlyList screensaverTextures; - private readonly int maxEmitCount; - private readonly ImageEmitLocation imageEmitLocation; + private readonly IImageEmitDetails imageEmitDetails; private readonly Random random; - private int currentEmitCount; /// /// Initializes a new instance of the class. /// /// The counter to use to control when new images are emitted. - /// The description of the area of the screensaver. /// The of the /// current screensaver. /// A collection of available screensaver textures to /// randomly choose from when emitting an image. - /// The maximum number of images that can be emitted. When this - /// value is reached, this will stop. - /// The location that images will be emitted on the - /// screensaver. + /// The details describing where and how many images will be + /// emitted. /// , - /// , , or - /// is . + /// , , or + /// is . public ScreensaverImageEmitter( ScreensaverImageEmitterCounter counter, - ScreensaverArea screensaverArea, ScreensaverImageManager screensaverImageManager, IReadOnlyList screensaverTextures, - int maxEmitCount, - ImageEmitLocation imageEmitLocation) + IImageEmitDetails imageEmitDetails) + : this( + counter, + screensaverImageManager, + screensaverTextures, + imageEmitDetails, + new Random()) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The counter to use to control when new images are emitted. + /// The of the + /// current screensaver. + /// A collection of available screensaver textures to + /// randomly choose from when emitting an image. + /// The details describing where and how many images will be + /// emitted. + /// A pseudo-random number generator that can be used to generate + /// randomness in the . + /// , + /// , , + /// , or is + /// . + public ScreensaverImageEmitter( + ScreensaverImageEmitterCounter counter, + ScreensaverImageManager screensaverImageManager, + IReadOnlyList screensaverTextures, + IImageEmitDetails imageEmitDetails, + Random random) { ParameterValidation.IsNotNull(counter, nameof(counter)); - ParameterValidation.IsNotNull(screensaverArea, nameof(screensaverArea)); ParameterValidation.IsNotNull(screensaverImageManager, nameof(screensaverImageManager)); ParameterValidation.IsNotNull(screensaverTextures, nameof(screensaverTextures)); + ParameterValidation.IsNotNull(imageEmitDetails, nameof(imageEmitDetails)); + ParameterValidation.IsNotNull(random, nameof(random)); this.counter = counter; - this.screensaverArea = screensaverArea; this.screensaverImageManager = screensaverImageManager; this.screensaverTextures = screensaverTextures; - this.random = new Random(); - this.maxEmitCount = maxEmitCount; - this.imageEmitLocation = imageEmitLocation; + this.imageEmitDetails = imageEmitDetails; + this.random = random; } /// @@ -111,9 +133,10 @@ public void Update(GameTime gameTime) { if (this.IsRunning) { - if (this.currentEmitCount >= this.maxEmitCount) + var imageCount = this.screensaverImageManager.ImageCount; + if (imageCount >= this.imageEmitDetails.MaxImageEmitCount) { - this.Stop(); + this.StopIfNotInfiniteImageEmitMode(); } else if (this.screensaverTextures.Count > 0) { @@ -122,17 +145,13 @@ public void Update(GameTime gameTime) { var textureIndex = this.random.Next(0, this.screensaverTextures.Count); var texture = this.screensaverTextures[textureIndex]; - var (position, speed) = this.GetImageEmitDetails(texture); - var screensaverImageItem = new ScreensaverImageItem - { - Position = position, - Speed = speed, - Texture = texture - }; + var screensaverImageItem = + this.imageEmitDetails.CreateScreensaverImageItem(texture); this.screensaverImageManager.AddScreensaverImage(screensaverImageItem); - if (++this.currentEmitCount >= this.maxEmitCount) + imageCount = this.screensaverImageManager.ImageCount; + if (imageCount >= this.imageEmitDetails.MaxImageEmitCount) { - this.Stop(); + this.StopIfNotInfiniteImageEmitMode(); break; } } @@ -140,166 +159,11 @@ public void Update(GameTime gameTime) } } - private (Vector2 Position, Vector2 Speed) GetImageEmitDetails(Texture2D texture) - { - (Vector2, Vector2) imageEmitDetails; - switch (this.imageEmitLocation) - { - case ImageEmitLocation.RandomCorner: - imageEmitDetails = this.GetRandomCornerEmitDetails(texture); - break; - case ImageEmitLocation.Random: - imageEmitDetails = this.GetRandomEmitDetails(); - break; - case ImageEmitLocation.Center: - imageEmitDetails = this.GetCenterEmitDetails(texture); - break; - case ImageEmitLocation.BottomRight: - imageEmitDetails = this.GetBottomRightEmitDetails(); - break; - case ImageEmitLocation.TopRight: - imageEmitDetails = this.GetTopRightEmitDetails(texture); - break; - case ImageEmitLocation.TopLeft: - imageEmitDetails = this.GetTopLeftEmitDetails(texture); - break; - case ImageEmitLocation.BottomLeft: - default: - imageEmitDetails = this.GetBottomLeftEmitDetails(texture); - break; - } - - return imageEmitDetails; - } - - private (Vector2 Position, Vector2 Speed) GetRandomCornerEmitDetails(Texture2D texture) - { - Func<(Vector2, Vector2)>[] emitDetailsFunctions = - { - this.GetBottomRightEmitDetails, - () => this.GetTopRightEmitDetails(texture), - () => this.GetTopLeftEmitDetails(texture), - () => this.GetBottomLeftEmitDetails(texture) - }; - return emitDetailsFunctions[this.random.Next(0, emitDetailsFunctions.Length)](); - } - - private (Vector2 Position, Vector2 Speed) GetRandomEmitDetails() - { - var minX = this.screensaverArea.PrimaryGameBounds.Left; - var maxX = this.screensaverArea.PrimaryGameBounds.Right; - var minY = this.screensaverArea.PrimaryGameBounds.Top; - var maxY = this.screensaverArea.PrimaryGameBounds.Bottom; - var position = new Vector2( - this.random.NextFloat(minX, maxX), - this.random.NextFloat(minY, maxY)); - var speed = new Vector2( - this.random.NextFloat(MinSpeed, MaxSpeed), - this.random.NextFloat(MinSpeed, MaxSpeed)); - this.RandomlyNegateSpeed(ref speed); - return (position, speed); - } - - private (Vector2 Position, Vector2 Speed) GetCenterEmitDetails(Texture2D texture) - { - var originCenter = this.screensaverArea.PrimaryGameBounds.Center.ToVector2() - - (new Vector2(texture.Width, texture.Height) / 2f); - var minX = originCenter.X - PositionDistribution; - var maxX = originCenter.X + PositionDistribution; - var minY = originCenter.Y - PositionDistribution; - var maxY = originCenter.Y + PositionDistribution; - var position = new Vector2( - this.random.NextFloat(minX, maxX), - this.random.NextFloat(minY, maxY)); - var speed = new Vector2( - this.random.NextFloat(MinSpeed, MaxSpeed), - this.random.NextFloat(MinSpeed, MaxSpeed)); - this.RandomlyNegateSpeed(ref speed); - return (position, speed); - } - - private (Vector2 Position, Vector2 Speed) GetBottomRightEmitDetails() - { - var bottomRightPrimary = new Point( - this.screensaverArea.PrimaryGameBounds.Right, - this.screensaverArea.PrimaryGameBounds.Bottom); - var minX = bottomRightPrimary.X; - var maxX = bottomRightPrimary.X + TwoPositionDistribution; - var minY = bottomRightPrimary.Y; - var maxY = bottomRightPrimary.Y + TwoPositionDistribution; - var position = new Vector2( - this.random.NextFloat(minX, maxX), - this.random.NextFloat(minY, maxY)); - var speed = new Vector2( - -this.random.NextFloat(MinSpeed, MaxSpeed), - -this.random.NextFloat(MinSpeed, MaxSpeed)); - return (position, speed); - } - - private (Vector2 Position, Vector2 Speed) GetTopRightEmitDetails(Texture2D texture) + private void StopIfNotInfiniteImageEmitMode() { - var topRightPrimary = new Point( - this.screensaverArea.PrimaryGameBounds.Right, - this.screensaverArea.PrimaryGameBounds.Top); - var minX = topRightPrimary.X; - var maxX = topRightPrimary.X + TwoPositionDistribution; - var minY = topRightPrimary.Y - texture.Height - TwoPositionDistribution; - var maxY = topRightPrimary.Y - texture.Height; - var position = new Vector2( - this.random.NextFloat(minX, maxX), - this.random.NextFloat(minY, maxY)); - var speed = new Vector2( - -this.random.NextFloat(MinSpeed, MaxSpeed), - this.random.NextFloat(MinSpeed, MaxSpeed)); - return (position, speed); - } - - private (Vector2 Position, Vector2 Speed) GetTopLeftEmitDetails(Texture2D texture) - { - var topLeftPrimary = new Point( - this.screensaverArea.PrimaryGameBounds.Left, - this.screensaverArea.PrimaryGameBounds.Top); - var minX = topLeftPrimary.X - texture.Width - TwoPositionDistribution; - var maxX = topLeftPrimary.X - texture.Width; - var minY = topLeftPrimary.Y - texture.Height - TwoPositionDistribution; - var maxY = topLeftPrimary.Y - texture.Height; - var position = new Vector2( - this.random.NextFloat(minX, maxX), - this.random.NextFloat(minY, maxY)); - var speed = new Vector2( - this.random.NextFloat(MinSpeed, MaxSpeed), - this.random.NextFloat(MinSpeed, MaxSpeed)); - return (position, speed); - } - - private (Vector2 Position, Vector2 Speed) GetBottomLeftEmitDetails(Texture2D texture) - { - var bottomLeftPrimary = new Point( - this.screensaverArea.PrimaryGameBounds.Left, - this.screensaverArea.PrimaryGameBounds.Bottom); - var minX = bottomLeftPrimary.X - texture.Width - TwoPositionDistribution; - var maxX = bottomLeftPrimary.X - texture.Width; - var minY = bottomLeftPrimary.Y; - var maxY = bottomLeftPrimary.Y + TwoPositionDistribution; - var position = new Vector2( - this.random.NextFloat(minX, maxX), - this.random.NextFloat(minY, maxY)); - var speed = new Vector2( - this.random.NextFloat(MinSpeed, MaxSpeed), - -this.random.NextFloat(MinSpeed, MaxSpeed)); - return (position, speed); - } - - private void RandomlyNegateSpeed(ref Vector2 speed) - { - if (this.random.Next(0, 2) == 0) - { - speed.X = -speed.X; - } - - if (this.random.Next(0, 2) == 0) + if (!this.imageEmitDetails.IsInfiniteImageEmitMode) { - speed.Y = -speed.Y; + this.Stop(); } } } diff --git a/src/EgamiFlowScreensaver/ScreensaverImageEmitterCounter.cs b/src/EgamiFlowScreensaver/ScreensaverImageEmitterCounter.cs index 0ea376a..3a4f21e 100644 --- a/src/EgamiFlowScreensaver/ScreensaverImageEmitterCounter.cs +++ b/src/EgamiFlowScreensaver/ScreensaverImageEmitterCounter.cs @@ -62,10 +62,13 @@ public int UpdateEmit(GameTime gameTime) var emitCount = 0; this.elapsed += (float)gameTime.ElapsedGameTime.TotalSeconds; - while (this.elapsed > this.particleRateInverse) + if (!gameTime.IsRunningSlowly) { - ++emitCount; - this.elapsed -= this.particleRateInverse; + while (this.elapsed > this.particleRateInverse) + { + ++emitCount; + this.elapsed -= this.particleRateInverse; + } } return emitCount; diff --git a/src/EgamiFlowScreensaver/ScreensaverImageItem.cs b/src/EgamiFlowScreensaver/ScreensaverImageItem.cs index 9a2c5c0..ed50921 100644 --- a/src/EgamiFlowScreensaver/ScreensaverImageItem.cs +++ b/src/EgamiFlowScreensaver/ScreensaverImageItem.cs @@ -16,8 +16,11 @@ namespace Natsnudasoft.EgamiFlowScreensaver { + using System; + using System.Collections.Generic; using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; + using Natsnudasoft.NatsnudaLibrary; /// /// Provides a class that encapsulates the current state of an image in a @@ -25,19 +28,151 @@ namespace Natsnudasoft.EgamiFlowScreensaver /// public sealed class ScreensaverImageItem { + private readonly List behaviors; + + /// + /// Initializes a new instance of the class. + /// + /// The texture that represents the image of this + /// . + /// A collection of behaviours that should be attached to this + /// . + /// , or + /// is . + public ScreensaverImageItem( + Texture2D texture, + IEnumerable behaviors) + { + ParameterValidation.IsNotNull(texture, nameof(texture)); + + this.Texture = texture; + this.behaviors = new List(behaviors); + this.Scale = Vector2.One; + this.Color = Color.White; + this.Alpha = 1f; + } + /// /// Gets or sets the current position of this screensaver image. /// public Vector2 Position { get; set; } /// - /// Gets or sets the speed that this screensaver image moves. + /// Gets the texture of this screensaver image. + /// + public Texture2D Texture { get; } + + /// + /// Gets or sets the origin to use when drawing this screensaver image. + /// + public Vector2 Origin { get; set; } + + /// + /// Gets or sets the colour this screensaver image will be drawn as. /// - public Vector2 Speed { get; set; } + public Color Color { get; set; } + + /// + /// Gets or sets the alpha value (opacity) this screensaver image will be drawn at. + /// + public float Alpha { get; set; } + + /// + /// Gets or sets the rotation value of this screensaver image (in radians). + /// + public float Rotation { get; set; } + + /// + /// Gets or sets the effects to use for mirroring of this screensaver image. + /// + public SpriteEffects SpriteEffects { get; set; } + + /// + /// Gets or sets the current scale this screensaver image will be drawn at. + /// + public Vector2 Scale { get; set; } + + /// + /// Gets a value indicating whether or not this is in the + /// process of being destroyed. + /// + /// if this is in the + /// process of being destroyed; otherwise . + public bool IsDestroying { get; private set; } + + /// + /// Gets a value indicating whether or not this is + /// destroyed. + /// + /// if this is destroyed; + /// otherwise . + public bool IsDestroyed { get; private set; } + + /// + /// Performs any initialization steps required by this . + /// + public void Initialize() + { + foreach (var behavior in this.behaviors) + { + behavior.Initialize(this); + } + } + + /// + /// Performs any update steps required by this . + /// + /// A snapshot of the current game time. + public void Update(GameTime gameTime) + { + var shouldDestroy = true; + foreach (var behavior in this.behaviors) + { + if (!behavior.IsFinished) + { + behavior.Update(this, gameTime); + } + + if (shouldDestroy) + { + shouldDestroy = + this.IsDestroying && (!behavior.BlocksDestroy || behavior.IsFinished); + } + } + + this.IsDestroyed = shouldDestroy; + } + + /// + /// Draws this to the specified + /// . + /// + /// The sprite batch to draw to. + /// is + /// . + public void Draw(SpriteBatch spriteBatch) + { + ParameterValidation.IsNotNull(spriteBatch, nameof(spriteBatch)); + + spriteBatch.Draw( + this.Texture, + this.Position, + null, + this.Color * this.Alpha, + this.Rotation, + this.Origin, + this.Scale, + this.SpriteEffects, + 0f); + } /// - /// Gets or sets the texture of this screensaver image. + /// Requests that this be destroyed as soon as all + /// blocking behaviours have finished. /// - public Texture2D Texture { get; set; } + public void BeginDestroy() + { + this.IsDestroying = true; + } } } \ No newline at end of file diff --git a/src/EgamiFlowScreensaver/ScreensaverImageItemBehavior.cs b/src/EgamiFlowScreensaver/ScreensaverImageItemBehavior.cs new file mode 100644 index 0000000..6001cec --- /dev/null +++ b/src/EgamiFlowScreensaver/ScreensaverImageItemBehavior.cs @@ -0,0 +1,62 @@ +// +// Copyright (c) Adrian John Dunstan. All rights reserved. +// +// Licensed 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. +// + +namespace Natsnudasoft.EgamiFlowScreensaver +{ + using System; + using Microsoft.Xna.Framework; + using Natsnudasoft.NatsnudaLibrary; + + /// + /// Provides an abstract base class for behaviours that can be composed and applied to a + /// . + /// + public abstract class ScreensaverImageItemBehavior : IScreensaverImageItemBehavior + { + /// + /// Initializes a new instance of the class. + /// + /// The description of the area of the screensaver. + /// is + /// . + protected ScreensaverImageItemBehavior(ScreensaverArea screensaverArea) + { + ParameterValidation.IsNotNull(screensaverArea, nameof(screensaverArea)); + + this.ScreensaverArea = screensaverArea; + } + + /// + public bool IsFinished { get; protected set; } + + /// + public virtual bool BlocksDestroy + { + get => false; + } + + /// + /// Gets the description of the area of the screensaver. + /// + protected ScreensaverArea ScreensaverArea { get; } + + /// + public abstract void Initialize(ScreensaverImageItem screensaverImageItem); + + /// + public abstract void Update(ScreensaverImageItem screensaverImageItem, GameTime gameTime); + } +} \ No newline at end of file diff --git a/src/EgamiFlowScreensaver/ScreensaverImageManager.cs b/src/EgamiFlowScreensaver/ScreensaverImageManager.cs index b0010a3..f2ac238 100644 --- a/src/EgamiFlowScreensaver/ScreensaverImageManager.cs +++ b/src/EgamiFlowScreensaver/ScreensaverImageManager.cs @@ -28,7 +28,7 @@ namespace Natsnudasoft.EgamiFlowScreensaver public sealed class ScreensaverImageManager { private readonly ScreensaverArea screensaverArea; - private readonly List screensaverImageItems; + private readonly LinkedList screensaverImageItems; /// /// Initializes a new instance of the class. @@ -41,41 +41,35 @@ public ScreensaverImageManager(ScreensaverArea screensaverArea) ParameterValidation.IsNotNull(screensaverArea, nameof(screensaverArea)); this.screensaverArea = screensaverArea; - this.screensaverImageItems = new List(); + this.screensaverImageItems = new LinkedList(); + } + + /// + /// Gets the number of images currently managed by this instance. + /// + public int ImageCount + { + get => this.screensaverImageItems.Count; } /// /// Updates the state of this . /// - public void Update() + /// A snapshot of the current game time. + public void Update(GameTime gameTime) { - var screensaverBounds = this.screensaverArea.ScreensaverGameBounds; - foreach (var screensaverImageItem in this.screensaverImageItems) + var screensaverImageItemNode = this.screensaverImageItems.First; + while (screensaverImageItemNode != null) { - screensaverImageItem.Position += screensaverImageItem.Speed; - var positionX = screensaverImageItem.Position.X; - var positionY = screensaverImageItem.Position.Y; - var speedX = screensaverImageItem.Speed.X; - var speedY = screensaverImageItem.Speed.Y; - var width = screensaverImageItem.Texture.Width; - var height = screensaverImageItem.Texture.Height; - if ((speedX > 0 && positionX + width > screensaverBounds.Right) || - (speedX < 0 && positionX < screensaverBounds.Left)) + var nextNode = screensaverImageItemNode.Next; + var screensaverImageItem = screensaverImageItemNode.Value; + screensaverImageItem.Update(gameTime); + if (screensaverImageItem.IsDestroyed) { - screensaverImageItem.Speed = new Vector2(-speedX, speedY); - var newX = - speedX > 0 ? screensaverBounds.Right - width : screensaverBounds.Left; - screensaverImageItem.Position = new Vector2(newX, positionY); + this.screensaverImageItems.Remove(screensaverImageItemNode); } - if ((speedY > 0 && positionY + height > screensaverBounds.Bottom) || - (speedY < 0 && positionY < screensaverBounds.Top)) - { - screensaverImageItem.Speed = new Vector2(speedX, -speedY); - var newY = - speedY > 0 ? screensaverBounds.Bottom - height : screensaverBounds.Top; - screensaverImageItem.Position = new Vector2(positionX, newY); - } + screensaverImageItemNode = nextNode; } } @@ -92,10 +86,7 @@ public void Draw(SpriteBatch spriteBatch) foreach (var screensaverImageItem in this.screensaverImageItems) { - spriteBatch.Draw( - screensaverImageItem.Texture, - screensaverImageItem.Position, - Color.White); + screensaverImageItem.Draw(spriteBatch); } } @@ -110,7 +101,8 @@ public void AddScreensaverImage(ScreensaverImageItem screensaverImageItem) { ParameterValidation.IsNotNull(screensaverImageItem, nameof(screensaverImageItem)); - this.screensaverImageItems.Add(screensaverImageItem); + screensaverImageItem.Initialize(); + this.screensaverImageItems.AddFirst(screensaverImageItem); } } } \ No newline at end of file diff --git a/src/EgamiFlowScreensaver/TopLeftImageEmitDetails.cs b/src/EgamiFlowScreensaver/TopLeftImageEmitDetails.cs new file mode 100644 index 0000000..a2c26d0 --- /dev/null +++ b/src/EgamiFlowScreensaver/TopLeftImageEmitDetails.cs @@ -0,0 +1,119 @@ +// +// Copyright (c) Adrian John Dunstan. All rights reserved. +// +// Licensed 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. +// + +namespace Natsnudasoft.EgamiFlowScreensaver +{ + using System; + using System.Collections.Generic; + using System.Linq; + using Microsoft.Xna.Framework; + using Microsoft.Xna.Framework.Graphics; + using Natsnudasoft.EgamiFlowScreensaver.Config; + using Natsnudasoft.NatsnudaLibrary; + + /// + /// Provides a class capable of creating locations at the top left of a screen for a + /// . + /// + public sealed class TopLeftImageEmitDetails : ImageEmitDetails + { + /// + /// Initializes a new instance of the class. + /// + /// The description of the area of the screensaver. + /// The + /// representing the configured state of the screensaver. + /// A collection of factories that define how to create + /// behaviours that will be attached to any images emitted by a + /// . + /// , + /// , or + /// is . + public TopLeftImageEmitDetails( + ScreensaverArea screensaverArea, + ScreensaverConfiguration screensaverConfiguration, + IEnumerable> behaviorFactories) + : base(screensaverArea, screensaverConfiguration, behaviorFactories) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The description of the area of the screensaver. + /// The + /// representing the configured state of the screensaver. + /// A collection of factories that define how to create + /// behaviours that will be attached to any images emitted by a + /// . + /// A pseudo-random number generator that can be used to generate + /// randomness in the . + /// , + /// , , or + /// is . + public TopLeftImageEmitDetails( + ScreensaverArea screensaverArea, + ScreensaverConfiguration screensaverConfiguration, + IEnumerable> behaviorFactories, + Random random) + : base(screensaverArea, screensaverConfiguration, behaviorFactories, random) + { + } + + /// + /// is + /// . + public override ScreensaverImageItem CreateScreensaverImageItem(Texture2D texture) + { + ParameterValidation.IsNotNull(texture, nameof(texture)); + + var origin = new Vector2(texture.Width, texture.Height) / 2f; + var topLeftPrimary = new Point( + this.ScreensaverArea.PrimaryGameBounds.Left, + this.ScreensaverArea.PrimaryGameBounds.Top); + var maxX = topLeftPrimary.X - texture.Width + origin.X; + var minX = maxX - TwoPositionDistribution; + var maxY = topLeftPrimary.Y - texture.Height + origin.Y; + var minY = maxY - TwoPositionDistribution; + var position = new Vector2( + this.Random.NextFloat(minX, maxX), + this.Random.NextFloat(minY, maxY)); + return new ScreensaverImageItem( + texture, + this.BehaviorFactories.Select(f => f?.Invoke())) + { + Position = position, + Origin = origin + }; + } + + /// + protected override Func CreateDefaultMovingBehaviorFactory() + { + IScreensaverImageItemBehavior CreateDefaultMovingBehavior() + { + const float minSpeed = MovingScreensaverImageItemBehavior.DefaultMinSpeed; + const float maxSpeed = MovingScreensaverImageItemBehavior.DefaultMaxSpeed; + var speed = new Vector2( + this.Random.NextFloat(minSpeed, maxSpeed), + this.Random.NextFloat(minSpeed, maxSpeed)); + return new MovingScreensaverImageItemBehavior(this.ScreensaverArea, speed); + } + + return CreateDefaultMovingBehavior; + } + } +} \ No newline at end of file diff --git a/src/EgamiFlowScreensaver/TopRightImageEmitDetails.cs b/src/EgamiFlowScreensaver/TopRightImageEmitDetails.cs new file mode 100644 index 0000000..fcbe168 --- /dev/null +++ b/src/EgamiFlowScreensaver/TopRightImageEmitDetails.cs @@ -0,0 +1,119 @@ +// +// Copyright (c) Adrian John Dunstan. All rights reserved. +// +// Licensed 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. +// + +namespace Natsnudasoft.EgamiFlowScreensaver +{ + using System; + using System.Collections.Generic; + using System.Linq; + using Microsoft.Xna.Framework; + using Microsoft.Xna.Framework.Graphics; + using Natsnudasoft.EgamiFlowScreensaver.Config; + using Natsnudasoft.NatsnudaLibrary; + + /// + /// Provides a class capable of creating locations at the top right of a screen for a + /// . + /// + public sealed class TopRightImageEmitDetails : ImageEmitDetails + { + /// + /// Initializes a new instance of the class. + /// + /// The description of the area of the screensaver. + /// The + /// representing the configured state of the screensaver. + /// A collection of factories that define how to create + /// behaviours that will be attached to any images emitted by a + /// . + /// , + /// , or + /// is . + public TopRightImageEmitDetails( + ScreensaverArea screensaverArea, + ScreensaverConfiguration screensaverConfiguration, + IEnumerable> behaviorFactories) + : base(screensaverArea, screensaverConfiguration, behaviorFactories) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The description of the area of the screensaver. + /// The + /// representing the configured state of the screensaver. + /// A collection of factories that define how to create + /// behaviours that will be attached to any images emitted by a + /// . + /// A pseudo-random number generator that can be used to generate + /// randomness in the . + /// , + /// , , or + /// is . + public TopRightImageEmitDetails( + ScreensaverArea screensaverArea, + ScreensaverConfiguration screensaverConfiguration, + IEnumerable> behaviorFactories, + Random random) + : base(screensaverArea, screensaverConfiguration, behaviorFactories, random) + { + } + + /// + /// is + /// . + public override ScreensaverImageItem CreateScreensaverImageItem(Texture2D texture) + { + ParameterValidation.IsNotNull(texture, nameof(texture)); + + var origin = new Vector2(texture.Width, texture.Height) / 2f; + var topRightPrimary = new Point( + this.ScreensaverArea.PrimaryGameBounds.Right, + this.ScreensaverArea.PrimaryGameBounds.Top); + var minX = topRightPrimary.X + origin.X; + var maxX = minX + TwoPositionDistribution; + var maxY = topRightPrimary.Y - texture.Height + origin.Y; + var minY = maxY - TwoPositionDistribution; + var position = new Vector2( + this.Random.NextFloat(minX, maxX), + this.Random.NextFloat(minY, maxY)); + return new ScreensaverImageItem( + texture, + this.BehaviorFactories.Select(f => f?.Invoke())) + { + Position = position, + Origin = origin + }; + } + + /// + protected override Func CreateDefaultMovingBehaviorFactory() + { + IScreensaverImageItemBehavior CreateDefaultMovingBehavior() + { + const float minSpeed = MovingScreensaverImageItemBehavior.DefaultMinSpeed; + const float maxSpeed = MovingScreensaverImageItemBehavior.DefaultMaxSpeed; + var speed = new Vector2( + -this.Random.NextFloat(minSpeed, maxSpeed), + this.Random.NextFloat(minSpeed, maxSpeed)); + return new MovingScreensaverImageItemBehavior(this.ScreensaverArea, speed); + } + + return CreateDefaultMovingBehavior; + } + } +} \ No newline at end of file diff --git a/src/EgamiFlowScreensaver/TransitionScreensaverImageItemBehavior.cs b/src/EgamiFlowScreensaver/TransitionScreensaverImageItemBehavior.cs new file mode 100644 index 0000000..cdc271c --- /dev/null +++ b/src/EgamiFlowScreensaver/TransitionScreensaverImageItemBehavior.cs @@ -0,0 +1,247 @@ +// +// Copyright (c) Adrian John Dunstan. All rights reserved. +// +// Licensed 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. +// + +namespace Natsnudasoft.EgamiFlowScreensaver +{ + using System; + using Microsoft.Xna.Framework; + using Natsnudasoft.NatsnudaLibrary; + + /// + /// Provides an abstract base class for behaviours that can be composed and applied to a + /// and contain time based transitions. + /// + /// + public abstract class TransitionScreensaverImageItemBehavior : ScreensaverImageItemBehavior + { + private static readonly Action EmptyTransitionUpdateAction = + (s, p) => { }; + + private readonly Action transitionUpdateAction; + private readonly Action endTransitionUpdateAction; + private readonly double inverseTransitionTime; + private readonly double inverseEndTransitionTime; + private TimeSpan currentTime; + private bool isStartTransitionFinished; + private bool hasEndTransitionStarted; + + /// + /// Initializes a new instance of the + /// class. + /// + /// The description of the area of the screensaver. + /// The time that this transition behaviour will take to + /// transition from start to finish. + /// is + /// . + /// is less + /// than a zero time. + protected TransitionScreensaverImageItemBehavior( + ScreensaverArea screensaverArea, + TimeSpan transitionTime) + : this(screensaverArea, transitionTime, EmptyTransitionUpdateAction) + { + } + + /// + /// Initializes a new instance of the + /// class. + /// + /// The description of the area of the screensaver. + /// The time that this transition behaviour will take to + /// transition from start to finish. + /// The time the image item this behaviour is applied to + /// will take to transition when it is being destroyed. + /// is + /// . + /// , or + /// is less than a zero time. + protected TransitionScreensaverImageItemBehavior( + ScreensaverArea screensaverArea, + TimeSpan transitionTime, + TimeSpan endTransitionTime) + : this( + screensaverArea, + transitionTime, + EmptyTransitionUpdateAction, + endTransitionTime, + EmptyTransitionUpdateAction) + { + } + + /// + /// Initializes a new instance of the + /// class. + /// + /// The description of the area of the screensaver. + /// The time that this transition behaviour will take to + /// transition from start to finish. + /// An action to perform on the + /// this behaviour is being applied to each time the + /// transition position is updated. + /// , or + /// is . + /// is less + /// than a zero time. + protected TransitionScreensaverImageItemBehavior( + ScreensaverArea screensaverArea, + TimeSpan transitionTime, + Action transitionUpdateAction) + : base(screensaverArea) + { + ParameterValidation + .IsGreaterThanOrEqualTo(transitionTime, TimeSpan.Zero, nameof(transitionTime)); + ParameterValidation.IsNotNull(transitionUpdateAction, nameof(transitionUpdateAction)); + + this.inverseTransitionTime = 1d / transitionTime.Ticks; + this.transitionUpdateAction = transitionUpdateAction; + } + + /// + /// Initializes a new instance of the + /// class. + /// + /// The description of the area of the screensaver. + /// The time that this transition behaviour will take to + /// transition from start to finish. + /// An action to perform on the + /// this behaviour is being applied to each time the + /// transition position is updated. + /// The time the image item this behaviour is applied to + /// will take to transition when it is being destroyed. + /// An action to perform on the + /// this behaviour is being applied to each time the + /// transition position is updated when the image item is being destroyed. + /// , or + /// , or + /// is . + /// , or + /// is less than a zero time. + protected TransitionScreensaverImageItemBehavior( + ScreensaverArea screensaverArea, + TimeSpan transitionTime, + Action transitionUpdateAction, + TimeSpan endTransitionTime, + Action endTransitionUpdateAction) + : base(screensaverArea) + { + ParameterValidation + .IsGreaterThanOrEqualTo(transitionTime, TimeSpan.Zero, nameof(transitionTime)); + ParameterValidation.IsNotNull(transitionUpdateAction, nameof(transitionUpdateAction)); + ParameterValidation.IsGreaterThanOrEqualTo( + endTransitionTime, + TimeSpan.Zero, + nameof(endTransitionTime)); + ParameterValidation + .IsNotNull(endTransitionUpdateAction, nameof(endTransitionUpdateAction)); + + this.inverseTransitionTime = 1d / transitionTime.Ticks; + this.transitionUpdateAction = transitionUpdateAction; + this.inverseEndTransitionTime = 1d / endTransitionTime.Ticks; + this.endTransitionUpdateAction = endTransitionUpdateAction; + } + + /// + public override bool BlocksDestroy + { + get => this.endTransitionUpdateAction != null; + } + + /// + /// Gets the current value of the position of the transition time (between 0.0 and 1.0 + /// inclusive). + /// + protected float TransitionPosition { get; private set; } + + /// + /// Gets the current value of the position of the end transition time (between 0.0 and 1.0 + /// inclusive). + /// + protected float EndTransitionPosition { get; private set; } + + /// + /// is + /// . + public override void Initialize(ScreensaverImageItem screensaverImageItem) + { + ParameterValidation.IsNotNull(screensaverImageItem, nameof(screensaverImageItem)); + + this.transitionUpdateAction(screensaverImageItem, this.TransitionPosition); + } + + /// + /// , or + /// is . + public override void Update(ScreensaverImageItem screensaverImageItem, GameTime gameTime) + { + ParameterValidation.IsNotNull(screensaverImageItem, nameof(screensaverImageItem)); + ParameterValidation.IsNotNull(gameTime, nameof(gameTime)); + + this.currentTime += gameTime.ElapsedGameTime; + if (!screensaverImageItem.IsDestroying) + { + if (!this.isStartTransitionFinished) + { + this.UpdateStartTransition(screensaverImageItem); + } + } + else if (!this.BlocksDestroy) + { + this.IsFinished = true; + } + else + { + this.UpdateEndTransition(screensaverImageItem); + } + } + + private void UpdateStartTransition(ScreensaverImageItem screensaverImageItem) + { + this.TransitionPosition = (float)(this.currentTime.Ticks * this.inverseTransitionTime); + if (this.TransitionPosition >= 1f) + { + if (!this.BlocksDestroy) + { + this.IsFinished = true; + } + + this.isStartTransitionFinished = true; + this.TransitionPosition = 1f; + } + + this.transitionUpdateAction(screensaverImageItem, this.TransitionPosition); + } + + private void UpdateEndTransition(ScreensaverImageItem screensaverImageItem) + { + if (!this.hasEndTransitionStarted) + { + this.currentTime = TimeSpan.Zero; + this.hasEndTransitionStarted = true; + } + + this.EndTransitionPosition = + (float)(this.currentTime.Ticks * this.inverseEndTransitionTime); + if (this.EndTransitionPosition >= 1f) + { + this.IsFinished = true; + this.EndTransitionPosition = 1f; + } + + this.endTransitionUpdateAction(screensaverImageItem, this.EndTransitionPosition); + } + } +} \ No newline at end of file diff --git a/src/EgamiFlowScreensaver/app.config b/src/EgamiFlowScreensaver/app.config index 85a6a30..529b889 100644 --- a/src/EgamiFlowScreensaver/app.config +++ b/src/EgamiFlowScreensaver/app.config @@ -3,36 +3,4 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/src/EgamiFlowScreensaver/packages.config b/src/EgamiFlowScreensaver/packages.config index d83b9fe..c65b821 100644 --- a/src/EgamiFlowScreensaver/packages.config +++ b/src/EgamiFlowScreensaver/packages.config @@ -9,20 +9,11 @@ - + - - - - - - - - - diff --git a/tools/install-mono-game.ps1 b/tools/install-mono-game.ps1 index c06a663..fa9bffe 100644 --- a/tools/install-mono-game.ps1 +++ b/tools/install-mono-game.ps1 @@ -1,5 +1,5 @@ # Install MonoGame Write-Output "Downloading MonoGame..." -(New-Object Net.WebClient).DownloadFile('https://github.com/MonoGame/MonoGame/releases/download/v3.7.1/MonoGameSetup.exe', 'C:\MonoGameSetup.exe') +(New-Object Net.WebClient).DownloadFile('http://www.monogame.net/releases/v3.6/MonoGameSetup.exe', 'C:\MonoGameSetup.exe') Write-Output "Installing MonoGame..." Invoke-Command -ScriptBlock {C:\MonoGameSetup.exe /S /v/qn} diff --git a/tools/sign-mono-game.ps1 b/tools/sign-mono-game.ps1 index 9a3d40d..8e5a116 100644 --- a/tools/sign-mono-game.ps1 +++ b/tools/sign-mono-game.ps1 @@ -1,7 +1,7 @@ # Sign MonoGame.Framework.dll IF (-Not (Test-Path Env:\APPVEYOR_PULL_REQUEST_NUMBER)) { Write-Output "Signing Monogame.Framework.dll with Natsnudasoft.snk..." - $monoGameDllPath = Get-ChildItem $PSScriptRoot\..\packages\MonoGame.Framework.WindowsDX.*\lib\net45\MonoGame.Framework.dll + $monoGameDllPath = Get-ChildItem $PSScriptRoot\..\packages\MonoGame.Framework.WindowsDX.*\lib\net40\MonoGame.Framework.dll $keyFilePath = "$PSScriptRoot\..\Natsnudasoft.snk" Start-Process $PSScriptRoot\..\Brutal.Dev.StrongNameSigner\build\StrongNameSigner.Console.exe -ArgumentList "-a $monoGameDllPath -k $keyFilePath" -NoNewWindow -Wait } \ No newline at end of file