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 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + +