diff --git a/NickvisionCavalier.GNOME/Blueprints/drawing_view.blp b/NickvisionCavalier.GNOME/Blueprints/drawing_view.blp
index 99d9ec7..8750335 100644
--- a/NickvisionCavalier.GNOME/Blueprints/drawing_view.blp
+++ b/NickvisionCavalier.GNOME/Blueprints/drawing_view.blp
@@ -20,6 +20,7 @@ Gtk.Stack _root {
child: Gtk.GLArea _glArea {
hexpand: true;
vexpand: true;
+ auto-render: false;
};
}
}
\ No newline at end of file
diff --git a/NickvisionCavalier.GNOME/Blueprints/preferences_dialog.blp b/NickvisionCavalier.GNOME/Blueprints/preferences_dialog.blp
index 7db5e9d..1b183bf 100644
--- a/NickvisionCavalier.GNOME/Blueprints/preferences_dialog.blp
+++ b/NickvisionCavalier.GNOME/Blueprints/preferences_dialog.blp
@@ -2,17 +2,75 @@ using Gtk 4.0;
using Adw 1;
Adw.PreferencesWindow _root {
- default-width: 600;
- default-height: 400;
+ default-width: 780;
+ default-height: 500;
modal: false;
destroy-with-parent: true;
- hide-on-close: false;
+ hide-on-close: true;
title: _("Preferences");
Adw.PreferencesPage {
title: _("Cavalier");
icon-name: "org.nickvision.cavalier-symbolic";
+ Adw.PreferencesGroup {
+ title: _("Drawing mode");
+
+ Adw.ActionRow {
+ title: _("Wave");
+ activatable-widget: _waveCheckButton;
+
+ [prefix]
+ Gtk.CheckButton _waveCheckButton {}
+ }
+
+ Adw.ActionRow {
+ title: _("Levels");
+ activatable-widget: _levelsCheckButton;
+
+ [prefix]
+ Gtk.CheckButton _levelsCheckButton {
+ group: _waveCheckButton;
+ }
+ }
+
+ Adw.ActionRow {
+ title: _("Particles");
+ activatable-widget: _particlesCheckButton;
+
+ [prefix]
+ Gtk.CheckButton _particlesCheckButton {
+ group: _waveCheckButton;
+ }
+ }
+
+ Adw.ActionRow {
+ title: _("Bars");
+ activatable-widget: _barsCheckButton;
+
+ [prefix]
+ Gtk.CheckButton _barsCheckButton {
+ group: _waveCheckButton;
+ }
+ }
+
+ Adw.ActionRow {
+ title: _("Spine");
+ activatable-widget: _spineCheckButton;
+
+ [prefix]
+ Gtk.CheckButton _spineCheckButton {
+ group: _waveCheckButton;
+ }
+ }
+ }
+
+ Adw.PreferencesGroup {
+ Adw.ComboRow _mirrorRow {
+ title: _("Mirror");
+ }
+ }
+
Adw.PreferencesGroup {
Adw.ActionRow {
title: _("Drawing area margin");
@@ -31,12 +89,86 @@ Adw.PreferencesWindow _root {
};
}
}
+
+ Adw.ComboRow _directionRow {
+ title: _("Drawing direction");
+ model: Gtk.StringList {
+ strings ["Top to bottom", "Bottom to top", "Left to right", "Right to left" ]
+ };
+ }
+
+ Adw.ActionRow _offsetRow {
+ title: _("Offset between items");
+ subtitle: _("The size of spaces between elements (in percent).");
+
+ [suffix]
+ Gtk.Scale _offsetScale {
+ width-request: 180;
+ draw-value: true;
+ value-pos: left;
+ digits: 0;
+ adjustment: Gtk.Adjustment {
+ lower: 0;
+ upper: 20;
+ step-increment: 1;
+ };
+ }
+ }
+
+ Adw.ActionRow _roundnessRow {
+ title: _("Roundness of items");
+ subtitle: _("How much rounded the elements should be (in percent).");
+
+ [suffix]
+ Gtk.Scale _roundnessScale {
+ width-request: 180;
+ draw-value: true;
+ value-pos: left;
+ digits: 0;
+ adjustment: Gtk.Adjustment {
+ lower: 0;
+ upper: 100;
+ step-increment: 1;
+ };
+ }
+ }
+
+ Adw.ActionRow {
+ title: _("Filling");
+ subtitle: _("Whether shapes should be filled or outlined.");
+ activatable-widget: _fillingSwitch;
+
+ [suffix]
+ Gtk.Switch _fillingSwitch {
+ valign: center;
+ }
+ }
+
+ Adw.ActionRow _thicknessRow {
+ title: _("Thickness of lines");
+ subtitle: _("Thickness of lines when filling is off (in pixels).");
+ sensitive: bind _fillingSwitch.active inverted;
+
+ [suffix]
+ Gtk.Scale _thicknessScale {
+ width-request: 180;
+ draw-value: true;
+ value-pos: left;
+ digits: 0;
+ adjustment: Gtk.Adjustment {
+ lower: 1;
+ upper: 10;
+ step-increment: 1;
+ };
+ }
+ }
}
Adw.PreferencesGroup {
Adw.ActionRow {
title: _("Borderless window");
subtitle: _("Whether to disable window shadow and borders.");
+ activatable-widget: _borderlessSwitch;
[suffix]
Gtk.Switch _borderlessSwitch {
@@ -47,6 +179,7 @@ Adw.PreferencesWindow _root {
Adw.ActionRow {
title: _("Sharp corners");
subtitle: _("Whether the main window corners should be sharp.");
+ activatable-widget: _sharpCornersSwitch;
[suffix]
Gtk.Switch _sharpCornersSwitch {
@@ -57,6 +190,7 @@ Adw.PreferencesWindow _root {
Adw.ActionRow {
title: _("Window controls");
subtitle: _("Whether to show window control buttons.");
+ activatable-widget: _windowControlsSwitch;
[suffix]
Gtk.Switch _windowControlsSwitch {
@@ -67,6 +201,7 @@ Adw.PreferencesWindow _root {
Adw.ActionRow {
title: _("Autohide headerbar");
subtitle: _("Whether to hide headerbar when main window is not focused.");
+ activatable-widget: _autohideHeaderSwitch;
[suffix]
Gtk.Switch _autohideHeaderSwitch {
@@ -109,6 +244,7 @@ Adw.PreferencesWindow _root {
Adw.ActionRow {
title: _("Automatic sensitivity");
subtitle: _("Attempt to decrease sensitivity if the bars peak.");
+ activatable-widget: _autosensSwitch;
[suffix]
Gtk.Switch _autosensSwitch {
@@ -155,6 +291,7 @@ Adw.PreferencesWindow _root {
Adw.ActionRow {
title: _("Monstercat smoothing");
subtitle: _("Whether to enable the so-called «Monstercat smoothing».");
+ activatable-widget: _monstercatSwitch;
[suffix]
Gtk.Switch _monstercatSwitch {
@@ -164,7 +301,7 @@ Adw.PreferencesWindow _root {
Adw.ActionRow {
title: _("Noise reduction");
- subtitle: _("This factor adjusts the integral and gravity filters to keep the signal smooth.\n1 will be very slow and smooth, 0 will be fast but noisy.");
+ subtitle: _("This factor adjusts the integral and gravity filters to keep the signal smooth.\nHigher value leads to a slower and smoother result.");
[suffix]
Gtk.Scale _noiseReductionScale {
@@ -173,8 +310,8 @@ Adw.PreferencesWindow _root {
value-pos: left;
digits: 2;
adjustment: Gtk.Adjustment {
- lower: 0.0;
- upper: 1.0;
+ lower: 0.15;
+ upper: 0.95;
step-increment: 0.01;
};
}
@@ -183,6 +320,7 @@ Adw.PreferencesWindow _root {
Adw.ActionRow {
title: _("Reverse order");
subtitle: _("Whether to reverse order of bars for each channel.");
+ activatable-widget: _reverseSwitch;
[suffix]
Gtk.Switch _reverseSwitch {
diff --git a/NickvisionCavalier.GNOME/Blueprints/window.blp b/NickvisionCavalier.GNOME/Blueprints/window.blp
index bf18040..c5b4d59 100644
--- a/NickvisionCavalier.GNOME/Blueprints/window.blp
+++ b/NickvisionCavalier.GNOME/Blueprints/window.blp
@@ -9,8 +9,8 @@ menu mainMenu {
}
Adw.ApplicationWindow _root {
- width-request: 170;
- height-request: 170;
+ width-request: 232;
+ height-request: 232;
Gtk.Overlay _overlay {
[overlay]
diff --git a/NickvisionCavalier.GNOME/Views/DrawingView.cs b/NickvisionCavalier.GNOME/Views/DrawingView.cs
index 2b25273..822de31 100644
--- a/NickvisionCavalier.GNOME/Views/DrawingView.cs
+++ b/NickvisionCavalier.GNOME/Views/DrawingView.cs
@@ -11,22 +11,35 @@ namespace NickvisionCavalier.GNOME.Views;
///
public partial class DrawingView : Gtk.Stack
{
+ public delegate bool GSourceFunc(nint data);
+
[LibraryImport("libEGL.so.1", StringMarshalling = StringMarshalling.Utf8)]
- private static partial IntPtr eglGetProcAddress(string name);
- //TODO: GLX and WGL
+ private static partial nint eglGetProcAddress(string name);
[LibraryImport("libGL.so.1", StringMarshalling = StringMarshalling.Utf8)]
private static partial void glClear(uint mask);
-
+ [LibraryImport("libadwaita-1.so.0", StringMarshalling = StringMarshalling.Utf8)]
+ private static partial void g_main_context_invoke(nint context, GSourceFunc function, nint data);
+
[Gtk.Connect] private readonly Gtk.GLArea _glArea;
private readonly DrawingViewController _controller;
private GRContext? _ctx;
private SKSurface? _skSurface;
private float[]? _sample;
-
+ private readonly GSourceFunc _queueRender;
+
private DrawingView(Gtk.Builder builder, DrawingViewController controller) : base(builder.GetPointer("_root"), false)
{
_controller = controller;
+ _queueRender = (x) =>
+ {
+ if (GetVisibleChildName() != "gl")
+ {
+ SetVisibleChildName("gl");
+ }
+ _glArea.QueueRender();
+ return false;
+ };
//Build UI
builder.Connect(this);
_glArea.OnRealize += (sender, e) =>
@@ -35,15 +48,11 @@ private DrawingView(Gtk.Builder builder, DrawingViewController controller) : bas
var grInt = GRGlInterface.Create(eglGetProcAddress);
_ctx = GRContext.CreateGl(grInt);
};
- _glArea.OnResize += OnResize;
+ _glArea.OnResize += (sender, e) => CreateSurface();
_controller.Cava.OutputReceived += (sender, sample) =>
{
- if (GetVisibleChildName() != "gl")
- {
- SetVisibleChildName("gl");
- }
_sample = sample;
- _glArea.QueueRender();
+ g_main_context_invoke(0, _queueRender, 0);
};
_glArea.OnRender += OnRender;
}
@@ -57,16 +66,17 @@ public DrawingView(DrawingViewController controller) : this(Builder.FromFile("dr
}
///
- /// (Re)creates surface on area resize
+ /// (Re)creates drawing surface
///
- /// Gtk.GLArea
- /// EventArgs
- private void OnResize(Gtk.GLArea sender, EventArgs e)
+ private void CreateSurface()
{
_skSurface?.Dispose();
- var imgInfo = new SKImageInfo(sender.GetAllocatedWidth(), sender.GetAllocatedHeight());
+ var imgInfo = new SKImageInfo(_glArea.GetAllocatedWidth(), _glArea.GetAllocatedHeight());
_skSurface = SKSurface.Create(_ctx, false, imgInfo);
- _controller.Canvas = _skSurface.Canvas;
+ if (_skSurface != null)
+ {
+ _controller.Canvas = _skSurface.Canvas;
+ }
}
///
diff --git a/NickvisionCavalier.GNOME/Views/MainWindow.cs b/NickvisionCavalier.GNOME/Views/MainWindow.cs
index 71f21ad..a75e3e5 100644
--- a/NickvisionCavalier.GNOME/Views/MainWindow.cs
+++ b/NickvisionCavalier.GNOME/Views/MainWindow.cs
@@ -21,23 +21,24 @@ public class MainWindow : Adw.ApplicationWindow
private readonly MainWindowController _controller;
private readonly Adw.Application _application;
private readonly DrawingView _drawingView;
+ private readonly PreferencesDialog _preferencesDialog;
private MainWindow(Gtk.Builder builder, MainWindowController controller, Adw.Application application) : base(builder.GetPointer("_root"), false)
{
//Window Settings
_controller = controller;
_application = application;
+ //Build UI
+ builder.Connect(this);
SetDefaultSize((int)_controller.WindowWidth, (int)_controller.WindowHeight);
SetTitle(_controller.AppInfo.ShortName);
SetIconName(_controller.AppInfo.ID);
- if (_controller.IsDevVersion)
- {
- AddCssClass("devel");
- }
- //Build UI
- builder.Connect(this);
_drawingView = new DrawingView(new DrawingViewController());
_overlay.SetChild(_drawingView);
+ var prefController = _controller.CreatePreferencesViewController();
+ prefController.OnWindowSettingsChanged += UpdateWindowSettings;
+ prefController.OnCavaSettingsChanged += _drawingView.UpdateCavaSettings;
+ _preferencesDialog = new PreferencesDialog(prefController, _application, this);
UpdateWindowSettings(null, EventArgs.Empty);
OnNotify += (sender, e) =>
{
@@ -53,7 +54,7 @@ private MainWindow(Gtk.Builder builder, MainWindowController controller, Adw.App
};
//Preferences Action
var actPreferences = Gio.SimpleAction.New("preferences", null);
- actPreferences.OnActivate += Preferences;
+ actPreferences.OnActivate += (sender, e) => _preferencesDialog.Present();
AddAction(actPreferences);
application.SetAccelsForAction("win.preferences", new string[] { "comma" });
//Keyboard Shortcuts Action
@@ -91,20 +92,6 @@ public void Start()
Present();
}
- ///
- /// Occurs when the preferences action is triggered
- ///
- /// Gio.SimpleAction
- /// EventArgs
- private void Preferences(Gio.SimpleAction sender, EventArgs e)
- {
- var prefController = _controller.CreatePreferencesViewController();
- prefController.OnWindowSettingsChanged += UpdateWindowSettings;
- prefController.OnCavaSettingsChanged += _drawingView.UpdateCavaSettings;
- var preferencesDialog = new PreferencesDialog(prefController, _application, this);
- preferencesDialog.Present();
- }
-
///
/// Occurs when settings for the window have changed
///
diff --git a/NickvisionCavalier.GNOME/Views/PreferencesDialog.cs b/NickvisionCavalier.GNOME/Views/PreferencesDialog.cs
index 1815899..9284007 100644
--- a/NickvisionCavalier.GNOME/Views/PreferencesDialog.cs
+++ b/NickvisionCavalier.GNOME/Views/PreferencesDialog.cs
@@ -1,5 +1,7 @@
using NickvisionCavalier.GNOME.Helpers;
using NickvisionCavalier.Shared.Controllers;
+using NickvisionCavalier.Shared.Models;
+using static NickvisionCavalier.Shared.Helpers.Gettext;
namespace NickvisionCavalier.GNOME.Views;
@@ -11,7 +13,21 @@ public partial class PreferencesDialog : Adw.PreferencesWindow
private readonly PreferencesViewController _controller;
private readonly Adw.Application _application;
+ [Gtk.Connect] private readonly Gtk.CheckButton _waveCheckButton;
+ [Gtk.Connect] private readonly Gtk.CheckButton _levelsCheckButton;
+ [Gtk.Connect] private readonly Gtk.CheckButton _particlesCheckButton;
+ [Gtk.Connect] private readonly Gtk.CheckButton _barsCheckButton;
+ [Gtk.Connect] private readonly Gtk.CheckButton _spineCheckButton;
+ [Gtk.Connect] private readonly Adw.ComboRow _mirrorRow;
[Gtk.Connect] private readonly Gtk.Scale _marginScale;
+ [Gtk.Connect] private readonly Adw.ComboRow _directionRow;
+ [Gtk.Connect] private readonly Adw.ActionRow _offsetRow;
+ [Gtk.Connect] private readonly Gtk.Scale _offsetScale;
+ [Gtk.Connect] private readonly Adw.ActionRow _roundnessRow;
+ [Gtk.Connect] private readonly Gtk.Scale _roundnessScale;
+ [Gtk.Connect] private readonly Gtk.Switch _fillingSwitch;
+ [Gtk.Connect] private readonly Adw.ActionRow _thicknessRow;
+ [Gtk.Connect] private readonly Gtk.Scale _thicknessScale;
[Gtk.Connect] private readonly Gtk.Switch _borderlessSwitch;
[Gtk.Connect] private readonly Gtk.Switch _sharpCornersSwitch;
[Gtk.Connect] private readonly Gtk.Switch _windowControlsSwitch;
@@ -32,12 +48,137 @@ private PreferencesDialog(Gtk.Builder builder, PreferencesViewController control
SetIconName(_controller.AppInfo.ID);
//Build UI
builder.Connect(this);
+ _waveCheckButton.OnToggled += (sender, e) =>
+ {
+ if (_waveCheckButton.GetActive())
+ {
+ _controller.Mode = DrawingMode.WaveBox;
+ _offsetRow.SetSensitive(false);
+ _roundnessRow.SetSensitive(false);
+ }
+ };
+ _levelsCheckButton.OnToggled += (sender, e) =>
+ {
+ if (_levelsCheckButton.GetActive())
+ {
+ _controller.Mode = DrawingMode.LevelsBox;
+ _offsetRow.SetSensitive(true);
+ _roundnessRow.SetSensitive(true);
+ }
+ };
+ _particlesCheckButton.OnToggled += (sender, e) =>
+ {
+ if (_particlesCheckButton.GetActive())
+ {
+ _controller.Mode = DrawingMode.ParticlesBox;
+ _offsetRow.SetSensitive(true);
+ _roundnessRow.SetSensitive(true);
+ }
+ };
+ _barsCheckButton.OnToggled += (sender, e) =>
+ {
+ if (_barsCheckButton.GetActive())
+ {
+ _controller.Mode = DrawingMode.BarsBox;
+ _offsetRow.SetSensitive(true);
+ _roundnessRow.SetSensitive(false);
+ }
+ };
+ _spineCheckButton.OnToggled += (sender, e) =>
+ {
+ if (_spineCheckButton.GetActive())
+ {
+ _controller.Mode = DrawingMode.SpineBox;
+ _offsetRow.SetSensitive(true);
+ _roundnessRow.SetSensitive(true);
+ }
+ };
+ switch (_controller.Mode)
+ {
+ case DrawingMode.WaveBox:
+ _waveCheckButton.SetActive(true);
+ break;
+ case DrawingMode.LevelsBox:
+ _levelsCheckButton.SetActive(true);
+ break;
+ case DrawingMode.ParticlesBox:
+ _particlesCheckButton.SetActive(true);
+ break;
+ case DrawingMode.BarsBox:
+ _barsCheckButton.SetActive(true);
+ break;
+ case DrawingMode.SpineBox:
+ _spineCheckButton.SetActive(true);
+ break;
+ }
+ if (_controller.Stereo)
+ {
+ _mirrorRow.SetModel(Gtk.StringList.New(new string[] { _("Off"), _("Full"), _("Split Channels") }));
+ _mirrorRow.SetSelected((uint)_controller.Mirror);
+ }
+ else
+ {
+ _mirrorRow.SetModel(Gtk.StringList.New(new string[] { _("Off"), _("On") }));
+ if (_controller.Mirror == Mirror.SplitChannels)
+ {
+ _mirrorRow.SetSelected(1u);
+ }
+ else
+ {
+ _mirrorRow.SetSelected((uint)_controller.Mirror);
+ }
+ }
+ _mirrorRow.OnNotify += (sender, e) =>
+ {
+ if (e.Pspec.GetName() == "selected")
+ {
+ _controller.Mirror = (Mirror)_mirrorRow.GetSelected();
+ _controller.Save();
+ }
+ };
_marginScale.SetValue((int)_controller.AreaMargin);
_marginScale.OnValueChanged += (sender, e) =>
{
_controller.AreaMargin = (uint)_marginScale.GetValue();
_controller.ChangeWindowSettings();
};
+ _directionRow.SetSelected((uint)_controller.Direction);
+ _directionRow.OnNotify += (sender, e) =>
+ {
+ if (e.Pspec.GetName() == "selected")
+ {
+ _controller.Direction = (DrawingDirection)_directionRow.GetSelected();
+ _controller.Save();
+ }
+ };
+ _offsetScale.SetValue((int)(_controller.ItemsOffset * 100));
+ _offsetScale.OnValueChanged += (sender, e) =>
+ {
+ _controller.ItemsOffset = (float)_offsetScale.GetValue() / 100.0f;
+ _controller.Save();
+ };
+ _roundnessScale.SetValue((int)(_controller.ItemsRoundness * 100));
+ _roundnessScale.OnValueChanged += (sender, e) =>
+ {
+ _controller.ItemsRoundness = (float)_roundnessScale.GetValue() / 100.0f;
+ _controller.Save();
+ };
+ _fillingSwitch.SetActive(_controller.Filling);
+ _fillingSwitch.OnNotify += (sender, e) =>
+ {
+ if (e.Pspec.GetName() == "active")
+ {
+ _controller.Filling = _fillingSwitch.GetActive();
+ _controller.Save();
+ }
+ };
+ _thicknessRow.SetSensitive(!_fillingSwitch.GetActive());
+ _thicknessScale.SetValue((int)_controller.LinesThickness);
+ _thicknessScale.OnValueChanged += (sender, e) =>
+ {
+ _controller.LinesThickness = (float)_thicknessScale.GetValue();
+ _controller.Save();
+ };
_borderlessSwitch.SetActive(_controller.Borderless);
_borderlessSwitch.OnNotify += (sender, e) =>
{
@@ -112,7 +253,16 @@ private PreferencesDialog(Gtk.Builder builder, PreferencesViewController control
_stereoButton.SetActive(_controller.Stereo);
_stereoButton.OnToggled += (sender, e) =>
{
- _controller.Stereo = _stereoButton.GetActive();
+ if (_stereoButton.GetActive())
+ {
+ _controller.Stereo = true;
+ _mirrorRow.SetModel(Gtk.StringList.New(new string[] { _("Off"), _("Full"), _("Split Channels") }));
+ }
+ else
+ {
+ _controller.Stereo = false;
+ _mirrorRow.SetModel(Gtk.StringList.New(new string[] { _("Off"), _("On") }));
+ }
_controller.ChangeCavaSettings();
};
_monstercatSwitch.SetActive(_controller.Monstercat);
@@ -138,6 +288,7 @@ private PreferencesDialog(Gtk.Builder builder, PreferencesViewController control
if (e.Pspec.GetName() == "active")
{
_controller.ReverseOrder = _reverseSwitch.GetActive();
+ _controller.Save();
}
};
}
diff --git a/NickvisionCavalier.Shared/Controllers/PreferencesViewController.cs b/NickvisionCavalier.Shared/Controllers/PreferencesViewController.cs
index f551d8b..aa14447 100644
--- a/NickvisionCavalier.Shared/Controllers/PreferencesViewController.cs
+++ b/NickvisionCavalier.Shared/Controllers/PreferencesViewController.cs
@@ -164,13 +164,88 @@ public bool ReverseOrder
set => Configuration.Current.ReverseOrder = value;
}
+ ///
+ /// Drawing direction
+ ///
+ public DrawingDirection Direction
+ {
+ get => Configuration.Current.Direction;
+
+ set => Configuration.Current.Direction = value;
+ }
+
+ ///
+ /// The size of spaces between elements
+ ///
+ public float ItemsOffset
+ {
+ get => Configuration.Current.ItemsOffset;
+
+ set => Configuration.Current.ItemsOffset = value;
+ }
+
+ ///
+ /// Roundness of items (0 - square, 1 - round)
+ ///
+ public float ItemsRoundness
+ {
+ get => Configuration.Current.ItemsRoundness;
+
+ set => Configuration.Current.ItemsRoundness = value;
+ }
+
+ ///
+ /// Whether to fill or draw lines
+ ///
+ public bool Filling
+ {
+ get => Configuration.Current.Filling;
+
+ set => Configuration.Current.Filling = value;
+ }
+
+ ///
+ /// Thickness of lines when filling is off (in pixels)
+ ///
+ public float LinesThickness
+ {
+ get => Configuration.Current.LinesThickness;
+
+ set => Configuration.Current.LinesThickness = value;
+ }
+
+ ///
+ /// Active drawing mode
+ ///
+ public DrawingMode Mode
+ {
+ get => Configuration.Current.Mode;
+
+ set => Configuration.Current.Mode = value;
+ }
+
+ ///
+ /// Mirror mode
+ ///
+ public Mirror Mirror
+ {
+ get => Configuration.Current.Mirror;
+
+ set => Configuration.Current.Mirror = value;
+ }
+
+ ///
+ /// Saves the configuration to disk
+ ///
+ public void Save() => Configuration.Current.Save();
+
///
/// Occurs when a window's setting has changed
///
public void ChangeWindowSettings()
{
OnWindowSettingsChanged?.Invoke(this, EventArgs.Empty);
- Configuration.Current.Save();
+ Save();
}
///
@@ -179,6 +254,6 @@ public void ChangeWindowSettings()
public void ChangeCavaSettings()
{
OnCavaSettingsChanged?.Invoke(this, EventArgs.Empty);
- Configuration.Current.Save();
+ Save();
}
}
diff --git a/NickvisionCavalier.Shared/Models/Configuration.cs b/NickvisionCavalier.Shared/Models/Configuration.cs
index b75cac2..9549b53 100644
--- a/NickvisionCavalier.Shared/Models/Configuration.cs
+++ b/NickvisionCavalier.Shared/Models/Configuration.cs
@@ -77,6 +77,34 @@ public class Configuration
/// Whether to reverse bars order for each channel
///
public bool ReverseOrder { get; set; }
+ ///
+ /// Drawing direction
+ ///
+ public DrawingDirection Direction { get; set; }
+ ///
+ /// The size of spaces between elements
+ ///
+ public float ItemsOffset { get; set; }
+ ///
+ /// Roundness of items (0 - square, 1 - round)
+ ///
+ public float ItemsRoundness { get; set; }
+ ///
+ /// Whether to fill or draw lines
+ ///
+ public bool Filling { get; set; }
+ ///
+ /// Thickness of lines when filling is off (in pixels)
+ ///
+ public float LinesThickness { get; set; }
+ ///
+ /// Active drawing mode
+ ///
+ public DrawingMode Mode { get; set; }
+ ///
+ /// Mirror mode
+ ///
+ public Mirror Mirror { get; set; }
///
/// Occurs when the configuration is saved to disk
@@ -108,6 +136,13 @@ public Configuration()
Monstercat = true;
NoiseReduction = 0.77f;
ReverseOrder = true;
+ Direction = DrawingDirection.BottomTop;
+ ItemsOffset = 0.1f;
+ ItemsRoundness = 0.5f;
+ Filling = true;
+ LinesThickness = 15;
+ Mode = DrawingMode.WaveBox;
+ Mirror = Mirror.Off;
}
///
diff --git a/NickvisionCavalier.Shared/Models/DrawingDirection.cs b/NickvisionCavalier.Shared/Models/DrawingDirection.cs
new file mode 100644
index 0000000..38c0b4f
--- /dev/null
+++ b/NickvisionCavalier.Shared/Models/DrawingDirection.cs
@@ -0,0 +1,12 @@
+namespace NickvisionCavalier.Shared.Models;
+
+///
+/// Drawing direction
+///
+public enum DrawingDirection
+{
+ TopBottom = 0,
+ BottomTop,
+ LeftRight,
+ RightLeft
+}
\ No newline at end of file
diff --git a/NickvisionCavalier.Shared/Models/DrawingMode.cs b/NickvisionCavalier.Shared/Models/DrawingMode.cs
new file mode 100644
index 0000000..1150178
--- /dev/null
+++ b/NickvisionCavalier.Shared/Models/DrawingMode.cs
@@ -0,0 +1,13 @@
+namespace NickvisionCavalier.Shared.Models;
+
+///
+/// Active drawing mode
+///
+public enum DrawingMode
+{
+ WaveBox = 0,
+ LevelsBox,
+ ParticlesBox,
+ BarsBox,
+ SpineBox
+}
\ No newline at end of file
diff --git a/NickvisionCavalier.Shared/Models/Mirror.cs b/NickvisionCavalier.Shared/Models/Mirror.cs
new file mode 100644
index 0000000..54b590c
--- /dev/null
+++ b/NickvisionCavalier.Shared/Models/Mirror.cs
@@ -0,0 +1,11 @@
+namespace NickvisionCavalier.Shared.Models;
+
+///
+/// Mirror mode
+///
+public enum Mirror
+{
+ Off = 0,
+ Full,
+ SplitChannels
+}
\ No newline at end of file
diff --git a/NickvisionCavalier.Shared/Models/Renderer.cs b/NickvisionCavalier.Shared/Models/Renderer.cs
index 478e80b..de69852 100644
--- a/NickvisionCavalier.Shared/Models/Renderer.cs
+++ b/NickvisionCavalier.Shared/Models/Renderer.cs
@@ -1,9 +1,14 @@
using SkiaSharp;
+using System;
+using System.Linq;
namespace NickvisionCavalier.Shared.Models;
public class Renderer
{
+ private delegate void DrawFunc(float[] sample, DrawingDirection direction, float x, float y, float width, float height, SKPaint paint);
+ private DrawFunc? _drawFunc;
+
public SKCanvas? Canvas { get; set; }
public Renderer()
@@ -18,20 +23,341 @@ public void Draw(float[] sample, float width, float height)
return;
}
Canvas.Clear();
- var paint = new SKPaint
+ var fgPaint = new SKPaint
{
- Style = SKPaintStyle.Fill,
+ Style = Configuration.Current.Filling ? SKPaintStyle.Fill : SKPaintStyle.Stroke,
+ StrokeWidth = Configuration.Current.LinesThickness,
Color = SKColors.Blue
};
- var step = width / sample.Length;
+ _drawFunc = Configuration.Current.Mode switch
+ {
+ DrawingMode.LevelsBox => DrawLevelsBox,
+ DrawingMode.ParticlesBox => DrawParticlesBox,
+ DrawingMode.BarsBox => DrawBarsBox,
+ DrawingMode.SpineBox => DrawSpineBox,
+ _ => DrawWaveBox,
+ };
+ if (Configuration.Current.Mirror == Mirror.Full)
+ {
+ _drawFunc(sample, Configuration.Current.Direction, 0, 0, GetMirrorWidth(width), GetMirrorHeight(height), fgPaint);
+ _drawFunc(sample, GetMirrorDirection(), GetMirrorX(width), GetMirrorY(height), GetMirrorWidth(width), GetMirrorHeight(height), fgPaint);
+ }
+ else if (Configuration.Current.Mirror == Mirror.SplitChannels)
+ {
+ _drawFunc(sample.Take(sample.Length / 2).ToArray(), Configuration.Current.Direction, 0, 0, GetMirrorWidth(width), GetMirrorHeight(height), fgPaint);
+ _drawFunc(sample.Skip(sample.Length / 2).Reverse().ToArray(), GetMirrorDirection(), GetMirrorX(width), GetMirrorY(height), GetMirrorWidth(width), GetMirrorHeight(height), fgPaint);
+ }
+ else
+ {
+ _drawFunc(sample, Configuration.Current.Direction, 0, 0, width, height, fgPaint);
+ }
+ Canvas.Flush();
+ }
+
+ private DrawingDirection GetMirrorDirection()
+ {
+ return Configuration.Current.Direction switch
+ {
+ DrawingDirection.TopBottom => DrawingDirection.BottomTop,
+ DrawingDirection.BottomTop => DrawingDirection.TopBottom,
+ DrawingDirection.LeftRight => DrawingDirection.RightLeft,
+ _ => DrawingDirection.LeftRight
+ };
+ }
+
+ private float GetMirrorX(float width)
+ {
+ if (Configuration.Current.Direction == DrawingDirection.LeftRight || Configuration.Current.Direction == DrawingDirection.RightLeft)
+ {
+ return width / 2.0f;
+ }
+ return 0;
+ }
+
+ private float GetMirrorY(float height)
+ {
+ if (Configuration.Current.Direction == DrawingDirection.TopBottom || Configuration.Current.Direction == DrawingDirection.BottomTop)
+ {
+ return height / 2.0f;
+ }
+ return 0;
+ }
+
+ private float GetMirrorWidth(float width)
+ {
+ if (Configuration.Current.Direction == DrawingDirection.LeftRight || Configuration.Current.Direction == DrawingDirection.RightLeft)
+ {
+ return width / 2.0f;
+ }
+ return width;
+ }
+
+ private float GetMirrorHeight(float height)
+ {
+ if (Configuration.Current.Direction == DrawingDirection.TopBottom || Configuration.Current.Direction == DrawingDirection.BottomTop)
+ {
+ return height / 2.0f;
+ }
+ return height;
+ }
+
+ private void DrawWaveBox(float[] sample, DrawingDirection direction, float x, float y, float width, float height, SKPaint paint)
+ {
+ var step = (direction < DrawingDirection.LeftRight ? width : height) / (sample.Length - 1);
+ using var path = new SKPath();
+ switch (direction)
+ {
+ case DrawingDirection.TopBottom:
+ path.MoveTo(x, y + height * sample[0] - (Configuration.Current.Filling ? 0 : Configuration.Current.LinesThickness / 2));
+ for (var i = 0; i < sample.Length - 1; i++)
+ {
+ path.CubicTo(
+ x + step * (i + 0.5f),
+ y + height * sample[i] - (Configuration.Current.Filling ? 0 : Configuration.Current.LinesThickness / 2),
+ x + step * (i + 0.5f),
+ y + height * sample[i+1] - (Configuration.Current.Filling ? 0 : Configuration.Current.LinesThickness / 2),
+ x + step * (i + 1),
+ y + height * sample[i+1] - (Configuration.Current.Filling ? 0 : Configuration.Current.LinesThickness / 2));
+ }
+ if (Configuration.Current.Filling)
+ {
+ path.LineTo(x + width, y);
+ path.LineTo(x, y);
+ path.Close();
+ }
+ break;
+ case DrawingDirection.BottomTop:
+ path.MoveTo(x, y + height * (1 - sample[0]) + (Configuration.Current.Filling ? 0 : Configuration.Current.LinesThickness / 2));
+ for (var i = 0; i < sample.Length - 1; i++)
+ {
+ path.CubicTo(
+ x + step * (i + 0.5f),
+ y + height * (1 - sample[i]) + (Configuration.Current.Filling ? 0 : Configuration.Current.LinesThickness / 2),
+ x + step * (i + 0.5f),
+ y + height * (1 - sample[i+1]) + (Configuration.Current.Filling ? 0 : Configuration.Current.LinesThickness / 2),
+ x + step * (i + 1),
+ y + height * (1 - sample[i+1]) + (Configuration.Current.Filling ? 0 : Configuration.Current.LinesThickness / 2));
+ }
+ if (Configuration.Current.Filling)
+ {
+ path.LineTo(x + width, y + height);
+ path.LineTo(x, y + height);
+ path.Close();
+ }
+ break;
+ case DrawingDirection.LeftRight:
+ path.MoveTo(x + width * sample[0] - (Configuration.Current.Filling ? 0 : Configuration.Current.LinesThickness / 2), y);
+ for (var i = 0; i < sample.Length - 1; i++)
+ {
+ path.CubicTo(
+ x + width * sample[i] - (Configuration.Current.Filling ? 0 : Configuration.Current.LinesThickness / 2),
+ y + step * (i + 0.5f),
+ x + width * sample[i+1] - (Configuration.Current.Filling ? 0 : Configuration.Current.LinesThickness / 2),
+ y + step * (i + 0.5f),
+ x + width * sample[i+1] - (Configuration.Current.Filling ? 0 : Configuration.Current.LinesThickness / 2),
+ y + step * (i + 1));
+ }
+ if (Configuration.Current.Filling)
+ {
+ path.LineTo(x, y + height);
+ path.LineTo(x, y);
+ path.Close();
+ }
+ break;
+ case DrawingDirection.RightLeft:
+ path.MoveTo(x + width * (1 - sample[0]) + (Configuration.Current.Filling ? 0 : Configuration.Current.LinesThickness / 2), y);
+ for (var i = 0; i < sample.Length - 1; i++)
+ {
+ path.CubicTo(
+ x + width * (1 - sample[i]) + (Configuration.Current.Filling ? 0 : Configuration.Current.LinesThickness / 2),
+ y + step * (i + 0.5f),
+ x + width * (1 - sample[i+1]) + (Configuration.Current.Filling ? 0 : Configuration.Current.LinesThickness / 2),
+ y + step * (i + 0.5f),
+ x + width * (1 - sample[i+1]) + (Configuration.Current.Filling ? 0 : Configuration.Current.LinesThickness / 2),
+ y + step * (i + 1));
+ }
+ if (Configuration.Current.Filling)
+ {
+ path.LineTo(x + width, y + height);
+ path.LineTo(x + width, y);
+ path.Close();
+ }
+ break;
+ }
+ Canvas.DrawPath(path, paint);
+ }
+
+ private void DrawLevelsBox(float[] sample, DrawingDirection direction, float x, float y, float width, float height, SKPaint paint)
+ {
+ var step = (direction < DrawingDirection.LeftRight ? width : height) / sample.Length;
+ var itemWidth = (direction < DrawingDirection.LeftRight ? step : width / 10) * (1 - Configuration.Current.ItemsOffset * 2) - (Configuration.Current.Filling ? 0 : Configuration.Current.LinesThickness / 2);
+ var itemHeight = (direction < DrawingDirection.LeftRight ? height / 10 : step) * (1 - Configuration.Current.ItemsOffset * 2) - (Configuration.Current.Filling ? 0 : Configuration.Current.LinesThickness / 2);
+ for (var i = 0; i < sample.Length; i++)
+ {
+ for (var j = 0; j < Math.Floor(sample[i] * 10); j++)
+ {
+ switch (direction)
+ {
+ case DrawingDirection.TopBottom:
+ Canvas.DrawRoundRect(
+ x + step * (i + Configuration.Current.ItemsOffset) + (Configuration.Current.Filling ? 0 : Configuration.Current.LinesThickness / 2),
+ y + height / 10 * j + height / 10 * Configuration.Current.ItemsOffset + (Configuration.Current.Filling ? 0 : Configuration.Current.LinesThickness / 2),
+ itemWidth, itemHeight,
+ itemWidth / 2 * Configuration.Current.ItemsRoundness, itemHeight / 2 * Configuration.Current.ItemsRoundness,
+ paint);
+ break;
+ case DrawingDirection.BottomTop:
+ Canvas.DrawRoundRect(
+ x + step * (i + Configuration.Current.ItemsOffset) + (Configuration.Current.Filling ? 0 : Configuration.Current.LinesThickness / 2),
+ y + height / 10 * (9 - j) + height / 10 * Configuration.Current.ItemsOffset + (Configuration.Current.Filling ? 0 : Configuration.Current.LinesThickness / 2),
+ itemWidth, itemHeight,
+ itemWidth / 2 * Configuration.Current.ItemsRoundness, itemHeight / 2 * Configuration.Current.ItemsRoundness,
+ paint);
+ break;
+ case DrawingDirection.LeftRight:
+ Canvas.DrawRoundRect(
+ x + width / 10 * j + width / 10 * Configuration.Current.ItemsOffset + (Configuration.Current.Filling ? 0 : Configuration.Current.LinesThickness / 2),
+ y + step * (i + Configuration.Current.ItemsOffset) + (Configuration.Current.Filling ? 0 : Configuration.Current.LinesThickness / 2),
+ itemWidth, itemHeight,
+ itemWidth / 2 * Configuration.Current.ItemsRoundness, itemHeight / 2 * Configuration.Current.ItemsRoundness,
+ paint);
+ break;
+ case DrawingDirection.RightLeft:
+ Canvas.DrawRoundRect(
+ x + width / 10 * (9 - j) + width / 10 * Configuration.Current.ItemsOffset + (Configuration.Current.Filling ? 0 : Configuration.Current.LinesThickness / 2),
+ y + step * (i + Configuration.Current.ItemsOffset) + (Configuration.Current.Filling ? 0 : Configuration.Current.LinesThickness / 2),
+ itemWidth, itemHeight,
+ itemWidth / 2 * Configuration.Current.ItemsRoundness, itemHeight / 2 * Configuration.Current.ItemsRoundness,
+ paint);
+ break;
+ }
+ }
+ }
+ }
+
+ private void DrawParticlesBox(float[] sample, DrawingDirection direction, float x, float y, float width, float height, SKPaint paint)
+ {
+ var step = (direction < DrawingDirection.LeftRight ? width : height) / sample.Length;
+ var itemWidth = (direction < DrawingDirection.LeftRight ? step : width / 11) * (1 - Configuration.Current.ItemsOffset * 2) - (Configuration.Current.Filling ? 0 : Configuration.Current.LinesThickness / 2);
+ var itemHeight = (direction < DrawingDirection.LeftRight ? height / 11 : step) * (1 - Configuration.Current.ItemsOffset * 2) - (Configuration.Current.Filling ? 0 : Configuration.Current.LinesThickness / 2);
+ for (var i = 0; i < sample.Length; i++)
+ {
+ switch (direction)
+ {
+ case DrawingDirection.TopBottom:
+ Canvas.DrawRoundRect(
+ x + step * (i + Configuration.Current.ItemsOffset) + (Configuration.Current.Filling ? 0 : Configuration.Current.LinesThickness / 2),
+ y + height / 11 * 10 * sample[i] + height / 11 * Configuration.Current.ItemsOffset + (Configuration.Current.Filling ? 0 : Configuration.Current.LinesThickness / 2),
+ itemWidth, itemHeight,
+ itemWidth / 2 * Configuration.Current.ItemsRoundness, itemHeight / 2 * Configuration.Current.ItemsRoundness,
+ paint);
+ break;
+ case DrawingDirection.BottomTop:
+ Canvas.DrawRoundRect(
+ x + step * (i + Configuration.Current.ItemsOffset) + (Configuration.Current.Filling ? 0 : Configuration.Current.LinesThickness / 2),
+ y + height / 11 * 10 * (1 - sample[i]) + height / 11 * Configuration.Current.ItemsOffset + (Configuration.Current.Filling ? 0 : Configuration.Current.LinesThickness / 2),
+ itemWidth, itemHeight,
+ itemWidth / 2 * Configuration.Current.ItemsRoundness, itemHeight / 2 * Configuration.Current.ItemsRoundness,
+ paint);
+ break;
+ case DrawingDirection.LeftRight:
+ Canvas.DrawRoundRect(
+ x + width / 11 * 10 * sample[i] + width / 11 * Configuration.Current.ItemsOffset + (Configuration.Current.Filling ? 0 : Configuration.Current.LinesThickness / 2),
+ y + step * (i + Configuration.Current.ItemsOffset) + (Configuration.Current.Filling ? 0 : Configuration.Current.LinesThickness / 2),
+ itemWidth, itemHeight,
+ itemWidth / 2 * Configuration.Current.ItemsRoundness, itemHeight / 2 * Configuration.Current.ItemsRoundness,
+ paint);
+ break;
+ case DrawingDirection.RightLeft:
+ Canvas.DrawRoundRect(
+ x + width / 11 * 10 * (1 - sample[i]) + width / 11 * Configuration.Current.ItemsOffset + (Configuration.Current.Filling ? 0 : Configuration.Current.LinesThickness / 2),
+ y + step * (i + Configuration.Current.ItemsOffset) + (Configuration.Current.Filling ? 0 : Configuration.Current.LinesThickness / 2),
+ itemWidth, itemHeight,
+ itemWidth / 2 * Configuration.Current.ItemsRoundness, itemHeight / 2 * Configuration.Current.ItemsRoundness,
+ paint);
+ break;
+ }
+ }
+ }
+
+ private void DrawBarsBox(float[] sample, DrawingDirection direction, float x, float y, float width, float height, SKPaint paint)
+ {
+ var step = (direction < DrawingDirection.LeftRight ? width : height) / sample.Length;
for (var i = 0; i < sample.Length; i++)
{
if (sample[i] == 0)
{
continue;
}
- Canvas.DrawRect(step * (i + 0.1f), height * (1 - sample[i]), step * 0.8f, height * sample[i], paint);
+ switch (direction)
+ {
+ case DrawingDirection.TopBottom:
+ Canvas.DrawRect(
+ x + step * (i + Configuration.Current.ItemsOffset) + (Configuration.Current.Filling ? 0 : Configuration.Current.LinesThickness / 2),
+ Configuration.Current.Filling ? y : y + Configuration.Current.LinesThickness / 2,
+ step * (1 - Configuration.Current.ItemsOffset * 2) - (Configuration.Current.Filling ? 0 : Configuration.Current.LinesThickness),
+ height * sample[i] - (Configuration.Current.Filling ? 0 : Configuration.Current.LinesThickness),
+ paint);
+ break;
+ case DrawingDirection.BottomTop:
+ Canvas.DrawRect(
+ x + step * (i + Configuration.Current.ItemsOffset) + (Configuration.Current.Filling ? 0 : Configuration.Current.LinesThickness / 2),
+ y + height * (1 - sample[i]) + (Configuration.Current.Filling ? 0 : Configuration.Current.LinesThickness / 2),
+ step * (1 - Configuration.Current.ItemsOffset * 2) - (Configuration.Current.Filling ? 0 : Configuration.Current.LinesThickness),
+ height * sample[i] - (Configuration.Current.Filling ? 0 : Configuration.Current.LinesThickness),
+ paint);
+ break;
+ case DrawingDirection.LeftRight:
+ Canvas.DrawRect(
+ Configuration.Current.Filling ? x : x + Configuration.Current.LinesThickness / 2,
+ y + step * (i + Configuration.Current.ItemsOffset) + (Configuration.Current.Filling ? 0 : Configuration.Current.LinesThickness / 2),
+ width * sample[i] - (Configuration.Current.Filling ? 0 : Configuration.Current.LinesThickness),
+ step * (1 - Configuration.Current.ItemsOffset * 2) - (Configuration.Current.Filling ? 0 : Configuration.Current.LinesThickness),
+ paint);
+ break;
+ case DrawingDirection.RightLeft:
+ Canvas.DrawRect(
+ x + width * (1 - sample[i]) + (Configuration.Current.Filling ? 0 : Configuration.Current.LinesThickness / 2),
+ y + step * (i + Configuration.Current.ItemsOffset) + (Configuration.Current.Filling ? 0 : Configuration.Current.LinesThickness / 2),
+ width * sample[i] - (Configuration.Current.Filling ? 0 : Configuration.Current.LinesThickness),
+ step * (1 - Configuration.Current.ItemsOffset * 2) - (Configuration.Current.Filling ? 0 : Configuration.Current.LinesThickness),
+ paint);
+ break;
+ };
+ }
+ }
+
+ private void DrawSpineBox(float[] sample, DrawingDirection direction, float x, float y, float width, float height, SKPaint paint)
+ {
+ var step = (direction < DrawingDirection.LeftRight ? width : height) / sample.Length;
+ var itemSize = step * (1 - Configuration.Current.ItemsOffset * 2) - (Configuration.Current.Filling ? 0 : Configuration.Current.LinesThickness);
+ for (var i = 0; i < sample.Length; i++)
+ {
+ if (sample[i] == 0)
+ {
+ continue;
+ }
+ switch (direction)
+ {
+ case DrawingDirection.TopBottom:
+ case DrawingDirection.BottomTop:
+ Canvas.DrawRoundRect(
+ x + step * (i + 0.5f) + (1 - itemSize * sample[i]) / 2,
+ y + height / 2 - itemSize * sample[i] / 2,
+ itemSize * sample[i], itemSize * sample[i],
+ itemSize * sample[i] / 2 * Configuration.Current.ItemsRoundness, itemSize * sample[i] / 2 * Configuration.Current.ItemsRoundness,
+ paint);
+ break;
+ case DrawingDirection.LeftRight:
+ case DrawingDirection.RightLeft:
+ Canvas.DrawRoundRect(
+ x + width / 2 - itemSize * sample[i] / 2,
+ y + step * (i + 0.5f) + (1 - itemSize * sample[i]) / 2,
+ itemSize * sample[i], itemSize * sample[i],
+ itemSize * sample[i] / 2 * Configuration.Current.ItemsRoundness, itemSize * sample[i] / 2 * Configuration.Current.ItemsRoundness,
+ paint);
+ break;
+ }
}
- Canvas.Flush();
}
}
diff --git a/NickvisionCavalier.Shared/Resources/org.nickvision.cavalier-devel.svg b/NickvisionCavalier.Shared/Resources/org.nickvision.cavalier-devel.svg
index 51c42ba..b5bbbe9 100644
--- a/NickvisionCavalier.Shared/Resources/org.nickvision.cavalier-devel.svg
+++ b/NickvisionCavalier.Shared/Resources/org.nickvision.cavalier-devel.svg
@@ -1,75 +1,2971 @@
-
-