* Profile editor - Fix undoing profile editor commands
- Undoing a change to an existing keyframe's value restores the value instead of deleting the keyframe
- Undoing "remove effect" puts the effect back in its place instead of below the next one
- Undoing "toggle keyframes" restores what it was before
- Undoing a brush change on a layer that had no brush removes the brush again, using the new Layer.RemoveLayerBrush
- Undoing "add element" disables the element again
- Undoing an event path change back to no path goes back to an event start node, like a new condition
- MoveProfileElement says "Move folder" for folders
- UpdateLayerProperty without a time no longer calls itself "Add ... keyframe"
* Profile editor - Fix auto-saving, failing commands, suspended editing and smaller editor issues
- A failed auto-save no longer stops auto-saving for the rest of the session
- A failing command is no longer also reported to ReactiveUI as unhandled, on top of the error dialog
- Resuming editing applies the editor's time again right away
- Nothing is focused while editing is suspended
- A layer created on another layer gets a name that isn't in use next to it
- CanUndo and CanRedo start out false, so new subscribers know the state right away
- An empty command scope doesn't add an undo step
- New subscribers to History get the current history once
- Expanding the keyframe selection with an already selected keyframe doesn't add it twice
- Snapping can snap to a snap time of zero
* Profile adaption - Fix arrow keys section including Page Down
- The arrow keys keyboard section now starts at Arrow Up instead of the key before it (Page Down)
- Updated the keyboard section test to expect only the four arrow keys
* Color quantizer - Let fully saturated colors be vibrant and grays be muted
- The saturation limits of the color types are now inclusive, so 100% saturated colors count as vibrant and 0% saturated colors count as muted
- The lightness limits stay exclusive so pure black and white are still left out
- Updated the quantizer tests to match
* Color gradients - Fix stop placement, seamless toggling and list events
- Stops on an occupied position now move down 0.001 instead of 0.002 (rounding error in the distance check)
- Flip, rotate, spread and interpolate move all stops together, so a stop can take a position another stop is leaving
- Spreading or randomizing a single stop puts it at 0 instead of NaN
- ToggleSeamless does nothing on an empty gradient and caps stretched stops at 1 instead of 100
- RemoveAt reports the removed stop and its index
- Insert links the stop like Add does and raises the add before the move
- Remove, RemoveAt, Clear and replacing a stop through the indexer unlink and stop listening to the actual removed stop
- Copying from a list of stops sorts them by position
- Obsolete GetPositionsArray no longer skips the seamless positions, obsolete GetColor picks the right colors after a repeat
- Updated the gradient tests to match
* Core - Fix small color, enum, string and type utility bugs
- Darken keeps the alpha of the color
- GetAllValuesAndDescriptions(Type) uses the [Description] attribute like the generic version
- UrlFriendly cuts titles at 80 characters instead of 81
- RemapInternationalCharToAscii knows both cases of r, l, d, thorn, sharp s, h and j with accents
- IsGenericType returns false instead of throwing when the base type is not generic
- IsCastableFrom also finds conversion operators declared on the target type
- GetDisplayName keeps the brackets of array types when humanizing
- Updated the utility tests to match
* Core - Fix removing dynamic children and checking which properties are in use
- DataModel.RemoveDynamicChild compared the value of each child with the
child that was passed in, so it never removed anything.
- IsPropertyInUse with includeChildren matched paths by their start, so
a path to "Integers" made "Integer" count as in use. Children are now
matched on what follows a dot.
* Core - Fix data model paths around saving, removed children and stored types
- The constructors saved the path before setting it up, while it was
still invalid, so a new path stored nothing and didn't follow its data
model leaving and returning to the store until it was saved.
- Removing the dynamic child a path points to left the path valid and
reading a default value, it now becomes invalid like when the child
was never there.
- The stored type of a path is now also looked up in all loaded
assemblies, Type.GetType only found it in Artemis.Core and the .NET
libraries, not in plugins or SkiaSharp.
* Core - Only let event conditions react to what happens after they start watching
- A trigger from before the condition watched its event, for example
long before the profile was activated, counted as new and played the
element once. Triggers now count from the moment the condition starts
watching its path.
- A condition on a plain value treated the first value it saw as a
change and played the element on the first frame. The first value is
now taken as the starting value.
* Core - Keep the name of renamed layer effects when loading
BaseLayerEffect.Load checked HasBeenRenamed before reading it from the entity, so renamed effects always fell back to the descriptor's display name.
* Core - Apply layer effect filters once on layers
Layer.DelegateRendering composited the layer with the pre-processed paint and also handed that paint to the brush, so color and image filters of effects applied to the brush output and again when compositing. The brush now gets a copy of the paint without those filters, like folders which apply them once.
* Core - Disable folders that get suspended while enabled
Layer.Update disables a suspended layer but Folder.Update only disabled a folder once its timeline finished, so a folder suspended outside of the editor kept rendering its children.
* Core - Update device LEDs when rotating or scaling a device
The Rotation and Scale setters only updated the RGB.NET device, the Artemis LED rectangles and the device path kept their old positions and DeviceUpdated wasn't raised until X or Y was set.
* Core - Correct the docs of the Layer and Folder constructors
They claimed the new element adds itself to the child collection of the parent, it doesn't and callers rely on adding it themselves.
* Core - Apply layer opacity once for brushes that draw with the paint
Brushes drawing with a shader or image keep the alpha of the paint they are given, so the layer opacity and opacity effects applied to the brush output and again when compositing, 50% opacity rendered at 25%. The brush now gets an opaque paint, per-LED brushes already overrode the color. The solid color test brush now draws with a shader like the real solid brush so the opacity tests cover this.
* Tests - Describe expected faded colors by their opacity
Adds an AtOpacity helper so assertions read as red at 50% over black instead of a hand-computed 127, and uses it for layer opacity, opacity effects, folder fades, the profile fade and semi-transparent drawing.
* Core - Wrap timelines around by how far they went past the main segment
Repeating timelines wrapped around by the frame time rather than by how
far they went past the end of the main segment, so wrapping could be off
by up to a frame. A timeline that was already past the end, like the
finished timeline new layers start with, still starts the main segment
over.
* Tests - Mention the defaults the timeline wrap-around tests depend on
Sample each LED from its unsnapped absolute boundary instead of the
render-grid-aligned AbsoluteRectangle, ensuring at least one pixel and
clamping to the texture. Also apply the saved render scale at startup.
Refactors plugin handling for improved reliability and user experience.
- Removes built-in plugin copying, migrating to workshop-based installation
- Introduces a dedicated service for plugin interactions (enabling, disabling, removal, settings)
- Simplifies plugin enabling/disabling logic in the UI, centralizing the logic