diff --git a/src/Dasher.Windows/Assets/avalonia-logo.ico b/src/Dasher.Windows/Assets/avalonia-logo.ico deleted file mode 100644 index f7da8bb..0000000 Binary files a/src/Dasher.Windows/Assets/avalonia-logo.ico and /dev/null differ diff --git a/src/Dasher.Windows/Controls/DasherCanvas.cs b/src/Dasher.Windows/Controls/DasherCanvas.cs index 10aa43f..632384a 100644 --- a/src/Dasher.Windows/Controls/DasherCanvas.cs +++ b/src/Dasher.Windows/Controls/DasherCanvas.cs @@ -22,7 +22,6 @@ public partial class DasherCanvas : Control private EyeGazeIntegration? _eyeGazeIntegration; private bool _useEyeGazeInput; - private NativeBridge.OutputCallback? _outputCallback; private NativeBridge.MessageCallback? _messageCallback; private bool _callbacksRegistered; private int _lastScreenWidth; @@ -48,6 +47,9 @@ public DasherCanvas() public IntPtr GetHandle() => _handle; + public void PauseTimer() => _timer.Stop(); + public void ResumeTimer() => _timer.Start(); + public void Initialize(string dataDir, string userDir) { _handle = NativeBridge.dasher_create(dataDir, userDir, out var errorPtr); @@ -133,13 +135,6 @@ private void EnsureCallbacksRegistered() if (_callbacksRegistered) return; _callbacksRegistered = true; - try - { - _outputCallback = new NativeBridge.OutputCallback(OnOutputEvent); - NativeBridge.dasher_set_output_callback(_handle, _outputCallback, IntPtr.Zero); - } - catch { } - try { _messageCallback = new NativeBridge.MessageCallback(OnEngineMessage); @@ -221,18 +216,6 @@ private void OnTick(object? sender, EventArgs e) InvalidateVisual(); } - private void OnOutputEvent(int eventType, IntPtr textPtr, IntPtr userData) - { - var text = textPtr != IntPtr.Zero ? Marshal.PtrToStringUTF8(textPtr) ?? "" : ""; - Dispatcher.UIThread.Post(() => - { - if (eventType == 0) - OutputText += text; - else if (eventType == 1 && OutputText.Length >= text.Length) - OutputText = OutputText[..^text.Length]; - }); - } - private void OnEngineMessage(int messageType, IntPtr textPtr, IntPtr userData) { var text = textPtr != IntPtr.Zero ? Marshal.PtrToStringUTF8(textPtr) ?? "" : ""; diff --git a/src/Dasher.Windows/Controls/SettingsPanel.cs b/src/Dasher.Windows/Controls/SettingsPanel.cs index e2abbf7..ed8644f 100644 --- a/src/Dasher.Windows/Controls/SettingsPanel.cs +++ b/src/Dasher.Windows/Controls/SettingsPanel.cs @@ -117,19 +117,6 @@ private void ShowCategoryCore(string category) _panel.Children.Clear(); - var backBtn = new Button - { - Content = "\u2190 Back", - FontSize = 12, - FontWeight = FontWeight.Medium, - Foreground = new SolidColorBrush(Color.FromRgb(0x8B, 0x92, 0x9A)), - Background = Brushes.Transparent, - Padding = new Thickness(0, 0, 0, 6), - BorderThickness = new Thickness(0), - }; - backBtn.Click += (s, e) => BackRequested?.Invoke(this, EventArgs.Empty); - _panel.Children.Add(backBtn); - if (category == "Input") { var inputSourceRow = BuildInputSourceRow(); @@ -212,7 +199,6 @@ private void ShowCategoryCore(string category) } } - public event EventHandler? BackRequested; public event EventHandler<(EyeGazeIntegration.TrackerType trackerType, int udpPort)>? InputSourceChanged; public event EventHandler? JoystickRequested; public event Action? OutputFontChanged; @@ -743,27 +729,20 @@ private static bool IsColourPaletteParameter(ParameterDisplayInfo info) ? Marshal.PtrToStringUTF8(currentPalettePtr) ?? "" : ""; - var section = new StackPanel { Spacing = 8, Margin = new Thickness(0, 4, 0, 12) }; + var section = new StackPanel { Spacing = 10, Margin = new Thickness(0, 4, 0, 12) }; var header = new TextBlock { Text = "Colour Theme", - FontSize = 12, - FontWeight = FontWeight.Medium, - Foreground = new SolidColorBrush(Color.FromRgb(0x0F, 0x4B, 0x75)), + FontSize = 13, + FontWeight = FontWeight.SemiBold, + Foreground = Application.Current?.FindResource("TextPrimary") as IBrush ?? Brushes.Black, }; section.Children.Add(header); - var scroll = new ScrollViewer - { - HorizontalScrollBarVisibility = ScrollBarVisibility.Auto, - VerticalScrollBarVisibility = ScrollBarVisibility.Disabled, - }; - - var strip = new StackPanel + var wrap = new WrapPanel { Orientation = Orientation.Horizontal, - Spacing = 10, }; var colors = new int[4]; @@ -777,23 +756,24 @@ private static bool IsColourPaletteParameter(ParameterDisplayInfo info) var card = new Button { - Padding = new Thickness(0), + Padding = new Thickness(6), + Margin = new Thickness(0, 0, 8, 8), Background = Brushes.Transparent, BorderThickness = new Thickness(0), Cursor = new Cursor(StandardCursorType.Hand), Tag = name, }; - var cardContent = new StackPanel { Spacing = 4, HorizontalAlignment = HorizontalAlignment.Center }; + var cardContent = new StackPanel { Spacing = 6, HorizontalAlignment = HorizontalAlignment.Center }; var swatchRow = new StackPanel { Orientation = Orientation.Horizontal, Spacing = 2 }; for (int c = 0; c < 4; c++) { var swatch = new Border { - Width = 16, - Height = 24, - CornerRadius = new CornerRadius(2), + Width = 22, + Height = 28, + CornerRadius = new CornerRadius(3), Background = ArgbToBrush(colors[c]), }; swatchRow.Children.Add(swatch); @@ -802,29 +782,31 @@ private static bool IsColourPaletteParameter(ParameterDisplayInfo info) var swatchContainer = new Border { Child = swatchRow, - CornerRadius = new CornerRadius(4), + CornerRadius = new CornerRadius(5), BorderThickness = new Thickness(isSelected ? 2 : 1), BorderBrush = isSelected - ? new SolidColorBrush(Color.FromRgb(0x00, 0x53, 0x7F)) + ? (Application.Current?.FindResource("DeepNavy") as IBrush ?? Brushes.Navy) : new SolidColorBrush(Color.FromArgb(0x4D, 0x80, 0x80, 0x80)), - Padding = new Thickness(1), + Padding = new Thickness(2), }; cardContent.Children.Add(swatchContainer); var nameLabel = new TextBlock { Text = name, - FontSize = 9, + FontSize = 11, Foreground = isSelected - ? new SolidColorBrush(Color.FromRgb(0x0F, 0x4B, 0x75)) - : new SolidColorBrush(Color.FromRgb(0x8B, 0x92, 0x9A)), + ? (Application.Current?.FindResource("TextPrimary") as IBrush ?? Brushes.Black) + : (Application.Current?.FindResource("TextSecondary") as IBrush ?? Brushes.Gray), HorizontalAlignment = HorizontalAlignment.Center, TextTrimming = TextTrimming.CharacterEllipsis, + Width = 100, + TextAlignment = TextAlignment.Center, }; cardContent.Children.Add(nameLabel); card.Content = cardContent; - card.Width = 80; + card.Width = 110; card.Click += (s, e) => { @@ -832,11 +814,10 @@ private static bool IsColourPaletteParameter(ParameterDisplayInfo info) NativeBridge.dasher_set_palette(_handle, paletteName); }; - strip.Children.Add(card); + wrap.Children.Add(card); } - scroll.Content = strip; - section.Children.Add(scroll); + section.Children.Add(wrap); return section; } diff --git a/src/Dasher.Windows/Views/MainWindow.axaml b/src/Dasher.Windows/Views/MainWindow.axaml index 0879684..47048ab 100644 --- a/src/Dasher.Windows/Views/MainWindow.axaml +++ b/src/Dasher.Windows/Views/MainWindow.axaml @@ -87,35 +87,36 @@ - - - - - + + + + + + + + + + + - + + + + + + + + + + + + + + + + + + + + diff --git a/src/Dasher.Windows/Views/MainWindow.axaml.cs b/src/Dasher.Windows/Views/MainWindow.axaml.cs index 2a31d10..8440c83 100644 --- a/src/Dasher.Windows/Views/MainWindow.axaml.cs +++ b/src/Dasher.Windows/Views/MainWindow.axaml.cs @@ -1,14 +1,12 @@ using System; +using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Linq; using System.Runtime.InteropServices; using System.Threading.Tasks; using Avalonia; -using Avalonia.Collections; using Avalonia.Controls; -using Avalonia.Controls.Primitives; -using Avalonia.Input; using Avalonia.Interactivity; using Avalonia.Layout; using Avalonia.Media; @@ -27,9 +25,8 @@ public partial class MainWindow : Window private DasherCanvas? _canvas; private MainWindowViewModel? _vm; private string _previousOutput = ""; - private Button[]? _prefsTabs; - private Border? _prefsTabStrip; - private Border? _prefsContentPanel; + private Button[]? _settingsTabs; + private bool _settingsInitialized; private NativeBridge.SpeakCallback? _speakCallback; private NativeBridge.ParameterCallback? _parameterCallback; private int _bitrateKey; @@ -132,9 +129,6 @@ protected override void OnOpened(EventArgs e) _vm = DataContext as MainWindowViewModel; if (_canvas == null || _vm == null) return; - _prefsTabStrip = this.FindControl("PrefsTabStrip"); - _prefsContentPanel = this.FindControl("PrefsContentPanel"); - var appData = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData); var dataDir = Path.Combine(appData, "Dasher"); Directory.CreateDirectory(dataDir); @@ -180,18 +174,6 @@ protected override void OnOpened(EventArgs e) ? Marshal.PtrToStringUTF8(currentAlphaPtr) ?? "" : ""; _vm.SelectedLanguageIndex = Math.Max(0, _vm.Languages.IndexOf(currentAlpha)); - var settingsPanel = this.FindControl("SettingsPanel"); - if (settingsPanel != null) - { - settingsPanel.Initialize(_vm.Handle); - settingsPanel.BackRequested += OnSettingsBack; - settingsPanel.InputSourceChanged += OnInputSourceChanged; - settingsPanel.JoystickRequested += OnJoystickRequested; - settingsPanel.OutputFontChanged += OnOutputFontChanged; - settingsPanel.KeyboardOpacityChanged += OnKeyboardOpacityChanged; - BuildPrefsTabs(settingsPanel); - } - ApplyOutputFontSettings(); var paneSettings = PaneSettings.Load(); @@ -390,7 +372,11 @@ private void ApplyPaneLayout() if (isKeyboard) { - // Keyboard mode: canvas only, no message pane + // Keyboard mode: hide full toolbar, show mini floating bar + TopBar.IsVisible = false; + KeyboardMiniBar.IsVisible = true; + + // Canvas only, no message pane MainGrid.ColumnDefinitions.Add(new ColumnDefinition(new GridLength(1, GridUnitType.Star))); MainGrid.Children.Add(DasherCanvas); Grid.SetColumn(DasherCanvas, 0); @@ -418,6 +404,9 @@ private void ApplyPaneLayout() } else { + TopBar.IsVisible = true; + KeyboardMiniBar.IsVisible = false; + Topmost = false; this.Opacity = 1.0; SetNoActivate(false); @@ -510,109 +499,122 @@ private void OnBack(object? sender, RoutedEventArgs e) { } - private void ApplyMode() + private void OnTogglePrefs(object? sender, RoutedEventArgs e) { if (_vm == null) return; + var dock = this.FindControl("SettingsDock"); + if (dock == null) return; - var txtKeyboardLabel = this.FindControl("TxtKeyboardLabel"); + var wasVisible = dock.IsVisible; + dock.IsVisible = !wasVisible; - if (_vm.IsKeyboardMode) + if (sender is Button btn) { - Topmost = true; - MessagePane.IsVisible = false; - MessageSplitter.IsVisible = false; - TxtModeLabel.Text = "Keyboard"; - BtnMode.Classes.Add("accent"); - if (txtKeyboardLabel != null) txtKeyboardLabel.Text = "Exit"; - BtnKeyboard.Classes.Add("accent"); - Width = Math.Min(Width, 600); + if (!wasVisible) btn.Classes.Add("accent"); + else btn.Classes.Remove("accent"); } - else + + if (!wasVisible && !_settingsInitialized) { - Topmost = false; - MessagePane.IsVisible = true; - MessageSplitter.IsVisible = true; - TxtModeLabel.Text = "Right side"; - BtnMode.Classes.Remove("accent"); - if (txtKeyboardLabel != null) txtKeyboardLabel.Text = "Keyboard"; - BtnKeyboard.Classes.Remove("accent"); - if (Width < 700) Width = 900; + InitializeSettingsPanel(); + _settingsInitialized = true; } - _previousOutput = _vm.OutputText; - } - - private void OnTogglePrefs(object? sender, RoutedEventArgs e) - { - if (_vm == null || sender is not Button btn) return; - _vm.IsPrefsVisible = !_vm.IsPrefsVisible; + // In keyboard mode, hide mini-bar while settings are open + if (_vm.IsKeyboardMode) + KeyboardMiniBar.IsVisible = wasVisible; // show when closing settings, hide when opening - if (_vm.IsPrefsVisible) - btn.Classes.Add("accent"); - else + // Pause/resume canvas timer when settings are open + if (_canvas != null) { - btn.Classes.Remove("accent"); - if (_prefsTabStrip != null) _prefsTabStrip.IsVisible = true; - if (_prefsContentPanel != null) _prefsContentPanel.IsVisible = false; - ActivatePrefsTab(-1); + if (!wasVisible) + _canvas.PauseTimer(); + else + _canvas.ResumeTimer(); } } - private void ActivatePrefsTab(int index) + private static readonly Dictionary SettingsTabIcons = new() { - if (_prefsTabs == null) return; - for (int i = 0; i < _prefsTabs.Length; i++) - { - if (i == index) _prefsTabs[i].Classes.Add("active"); - else _prefsTabs[i].Classes.Remove("active"); - } + { "Input", LucideIconKind.MousePointerClick }, + { "Language", LucideIconKind.Languages }, + { "Customization", LucideIconKind.Palette }, + { "Speed", LucideIconKind.Gauge }, + { "Output", LucideIconKind.Type }, + { "Speech", LucideIconKind.Volume2 }, + { "Appearance", LucideIconKind.Eye }, + { "Advanced", LucideIconKind.Wrench }, + { "Other", LucideIconKind.Ellipsis }, + }; + + private void InitializeSettingsPanel() + { + var panel = this.FindControl("DockedSettingsPanel"); + if (panel == null || _vm == null) return; + + panel.Initialize(_vm.Handle); + panel.OutputFontChanged += OnOutputFontChanged; + panel.KeyboardOpacityChanged += OnKeyboardOpacityChanged; + panel.InputSourceChanged += OnInputSourceChanged; + panel.JoystickRequested += OnJoystickRequested; + + BuildSettingsTabs(panel); } - private void BuildPrefsTabs(SettingsPanel settingsPanel) + private void BuildSettingsTabs(SettingsPanel settingsPanel) { - var container = this.FindControl("PrefsTabContainer"); + var container = this.FindControl("SettingsTabContainer"); if (container == null) return; container.Children.Clear(); - _prefsTabs = []; + _settingsTabs = []; foreach (var category in settingsPanel.GetCategoryNames()) { var btn = new Button { - Classes = { "prefs-tab" }, - Content = category, + Classes = { "settings-tab" }, Tag = category, }; - btn.Click += OnPrefsTabClick; + + var stack = new StackPanel + { + Orientation = Orientation.Horizontal, + Spacing = 6, + }; + var iconKind = SettingsTabIcons.GetValueOrDefault(category, LucideIconKind.Circle); + stack.Children.Add(new LucideIcon { Kind = iconKind, Size = 16 }); + stack.Children.Add(new TextBlock { Text = category }); + + btn.Content = stack; + btn.Click += OnSettingsTabClick; container.Children.Add(btn); - _prefsTabs = [.. _prefsTabs, btn]; + _settingsTabs = [.. _settingsTabs, btn]; } + + ActivateSettingsTab(0); + settingsPanel.ShowCategory(settingsPanel.GetCategoryNames().FirstOrDefault() ?? ""); } - private void OnPrefsTabClick(object? sender, RoutedEventArgs e) + private void OnSettingsTabClick(object? sender, RoutedEventArgs e) { - if (sender is not Button btn || _vm == null) return; + if (sender is not Button btn) return; var category = btn.Tag as string ?? ""; - ActivatePrefsTab(Array.FindIndex(_prefsTabs!, t => t.Tag as string == category)); - - var settingsPanel = this.FindControl("SettingsPanel"); - if (settingsPanel != null && _prefsTabStrip != null && _prefsContentPanel != null) - { - settingsPanel.ShowCategory(category); - _prefsTabStrip.IsVisible = false; - _prefsContentPanel.IsVisible = true; - } + var idx = Array.FindIndex(_settingsTabs!, t => t.Tag as string == category); + if (idx < 0) return; + ActivateSettingsTab(idx); + var panel = this.FindControl("DockedSettingsPanel"); + panel?.ShowCategory(category); } - private void OnSettingsBack(object? sender, EventArgs e) + private void ActivateSettingsTab(int index) { - if (_prefsTabStrip != null && _prefsContentPanel != null) + if (_settingsTabs == null) return; + for (int i = 0; i < _settingsTabs.Length; i++) { - _prefsTabStrip.IsVisible = true; - _prefsContentPanel.IsVisible = false; + if (i == index) _settingsTabs[i].Classes.Add("active"); + else _settingsTabs[i].Classes.Remove("active"); } - ActivatePrefsTab(-1); } private void OnNew(object? sender, RoutedEventArgs e) @@ -678,10 +680,6 @@ private async void OnSave(object? sender, RoutedEventArgs e) - private void OnStats(object? sender, RoutedEventArgs e) - { - } - private void OnLanguageChanged(object? sender, SelectionChangedEventArgs e) { if (_vm == null || _vm.Handle == IntPtr.Zero || _vm.SelectedLanguageIndex < 0) return; @@ -865,6 +863,12 @@ private void UpdateControlModeLabel() var label = this.FindControl("TxtControlLabel"); if (label != null) label.Text = _controlModeActive ? "Leave" : "Control"; + + // Sync mini-bar control button accent state + if (_controlModeActive) + KbControlBtn.Classes.Add("accent"); + else + KbControlBtn.Classes.Remove("accent"); } private void OnToggleGameMode(object? sender, RoutedEventArgs e)