Initial commit to new repository
This commit is contained in:
327
src/AxCopilot/Views/DockBarWindow.xaml.cs
Normal file
327
src/AxCopilot/Views/DockBarWindow.xaml.cs
Normal file
@@ -0,0 +1,327 @@
|
||||
using System.Diagnostics;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Threading;
|
||||
using AxCopilot.Services;
|
||||
|
||||
namespace AxCopilot.Views;
|
||||
|
||||
/// <summary>
|
||||
/// 화면 하단에 고정되는 미니 독 바.
|
||||
/// 설정(DockBarItems)에 따라 표시 항목이 결정됩니다.
|
||||
/// 가능한 항목: launcher, clipboard, capture, agent, clock, cpu, ram, quickinput
|
||||
/// </summary>
|
||||
public partial class DockBarWindow : Window
|
||||
{
|
||||
private DispatcherTimer? _timer;
|
||||
private PerformanceCounter? _cpuCounter;
|
||||
private TextBlock? _cpuText;
|
||||
private TextBlock? _ramText;
|
||||
private TextBlock? _clockText;
|
||||
private TextBox? _quickInput;
|
||||
|
||||
/// <summary>런처에 검색어를 전달하는 콜백.</summary>
|
||||
public Action<string>? OnQuickSearch { get; set; }
|
||||
|
||||
/// <summary>캡처 직접 실행 콜백.</summary>
|
||||
public Action? OnCapture { get; set; }
|
||||
|
||||
/// <summary>AX Agent 대화창 열기 콜백.</summary>
|
||||
public Action? OnOpenAgent { get; set; }
|
||||
|
||||
// 표시 가능한 모든 아이템 정의
|
||||
private static readonly (string Key, string Icon, string Tooltip)[] AllItems =
|
||||
{
|
||||
("launcher", "\uE721", "AX Commander"),
|
||||
("clipboard", "\uE77F", "클립보드 히스토리"),
|
||||
("capture", "\uE722", "화면 캡처"),
|
||||
("agent", "\uE8BD", "AX Agent"),
|
||||
("clock", "\uE823", "시계"),
|
||||
("cpu", "\uE950", "CPU"),
|
||||
("ram", "\uE7F4", "RAM"),
|
||||
("quickinput", "\uE8D3", "빠른 입력"),
|
||||
};
|
||||
|
||||
private DispatcherTimer? _glowTimer;
|
||||
|
||||
/// <summary>설정 저장 콜백 (위치 저장용).</summary>
|
||||
public Action<double, double>? OnPositionChanged { get; set; }
|
||||
|
||||
public DockBarWindow()
|
||||
{
|
||||
InitializeComponent();
|
||||
|
||||
MouseLeftButtonDown += (_, e) => { if (e.LeftButton == MouseButtonState.Pressed) try { DragMove(); } catch (Exception) { } };
|
||||
LocationChanged += (_, _) => OnPositionChanged?.Invoke(Left, Top);
|
||||
Loaded += (_, _) => PositionDock();
|
||||
Closed += (_, _) => { _timer?.Stop(); _glowTimer?.Stop(); _cpuCounter?.Dispose(); };
|
||||
}
|
||||
|
||||
/// <summary>투명도, 위치, 글로우를 설정에서 적용합니다.</summary>
|
||||
public void ApplySettings(double opacity, double left, double top, bool rainbowGlow)
|
||||
{
|
||||
Opacity = Math.Clamp(opacity, 0.3, 1.0);
|
||||
|
||||
if (left >= 0 && top >= 0)
|
||||
{
|
||||
Left = left;
|
||||
Top = top;
|
||||
}
|
||||
else
|
||||
{
|
||||
// -1이면 중앙으로 이동
|
||||
Dispatcher.BeginInvoke(System.Windows.Threading.DispatcherPriority.Loaded, PositionDock);
|
||||
}
|
||||
|
||||
if (rainbowGlow)
|
||||
StartRainbowGlow();
|
||||
else
|
||||
StopRainbowGlow();
|
||||
}
|
||||
|
||||
private void StartRainbowGlow()
|
||||
{
|
||||
RainbowGlowBorder.Visibility = Visibility.Visible;
|
||||
_glowTimer ??= new DispatcherTimer { Interval = TimeSpan.FromMilliseconds(50) };
|
||||
var startAngle = 0.0;
|
||||
_glowTimer.Tick += (_, _) =>
|
||||
{
|
||||
startAngle += 2;
|
||||
if (startAngle >= 360) startAngle -= 360;
|
||||
var rad = startAngle * Math.PI / 180.0;
|
||||
RainbowBrush.StartPoint = new Point(0.5 + 0.5 * Math.Cos(rad), 0.5 + 0.5 * Math.Sin(rad));
|
||||
RainbowBrush.EndPoint = new Point(0.5 - 0.5 * Math.Cos(rad), 0.5 - 0.5 * Math.Sin(rad));
|
||||
};
|
||||
_glowTimer.Start();
|
||||
}
|
||||
|
||||
private void StopRainbowGlow()
|
||||
{
|
||||
_glowTimer?.Stop();
|
||||
RainbowGlowBorder.Visibility = Visibility.Collapsed;
|
||||
}
|
||||
|
||||
// 기획된 고정 표시 순서
|
||||
private static readonly string[] FixedOrder =
|
||||
{ "launcher", "clipboard", "capture", "agent", "clock", "cpu", "ram", "quickinput" };
|
||||
|
||||
/// <summary>설정에서 표시 항목 목록을 받아 독 바를 빌드합니다. 표시 순서는 기획 순서 고정.</summary>
|
||||
public void BuildFromSettings(List<string> itemKeys)
|
||||
{
|
||||
DockContent.Children.Clear();
|
||||
_cpuText = null; _ramText = null; _clockText = null; _quickInput = null;
|
||||
|
||||
var primaryText = TryFindResource("PrimaryText") as Brush ?? Brushes.White;
|
||||
var secondaryText = TryFindResource("SecondaryText") as Brush ?? Brushes.Gray;
|
||||
var accentBrush = TryFindResource("AccentColor") as Brush ?? Brushes.CornflowerBlue;
|
||||
bool needTimer = false;
|
||||
bool addedFirst = false;
|
||||
|
||||
// 기획 순서에서 활성화된 항목만 순서대로 표시
|
||||
var enabledSet = new HashSet<string>(itemKeys, StringComparer.OrdinalIgnoreCase);
|
||||
foreach (var key in FixedOrder.Where(k => enabledSet.Contains(k)))
|
||||
{
|
||||
if (addedFirst) AddSeparator();
|
||||
addedFirst = true;
|
||||
|
||||
switch (key)
|
||||
{
|
||||
case "launcher":
|
||||
AddButton("\uE721", "AX Commander", primaryText, () => OnQuickSearch?.Invoke(""));
|
||||
break;
|
||||
case "clipboard":
|
||||
AddButton("\uE77F", "클립보드", primaryText, () => OnQuickSearch?.Invoke("#"));
|
||||
break;
|
||||
case "capture":
|
||||
AddButton("\uE722", "캡처", primaryText, () =>
|
||||
{
|
||||
if (OnCapture != null) OnCapture();
|
||||
else OnQuickSearch?.Invoke("cap");
|
||||
});
|
||||
break;
|
||||
case "agent":
|
||||
AddButton("\uE8BD", "AI", primaryText, () =>
|
||||
{
|
||||
if (OnOpenAgent != null) OnOpenAgent();
|
||||
else OnQuickSearch?.Invoke("!");
|
||||
});
|
||||
break;
|
||||
case "clock":
|
||||
_clockText = new TextBlock
|
||||
{
|
||||
Text = DateTime.Now.ToString("HH:mm"),
|
||||
FontSize = 12, FontWeight = FontWeights.SemiBold,
|
||||
Foreground = primaryText,
|
||||
VerticalAlignment = VerticalAlignment.Center,
|
||||
Margin = new Thickness(6, 0, 6, 0),
|
||||
};
|
||||
DockContent.Children.Add(_clockText);
|
||||
needTimer = true;
|
||||
break;
|
||||
case "cpu":
|
||||
var cpuPanel = new StackPanel { Orientation = Orientation.Horizontal, VerticalAlignment = VerticalAlignment.Center };
|
||||
cpuPanel.Children.Add(new TextBlock
|
||||
{
|
||||
Text = "\uE950", FontFamily = new FontFamily("Segoe MDL2 Assets"),
|
||||
FontSize = 11, Foreground = accentBrush,
|
||||
VerticalAlignment = VerticalAlignment.Center, Margin = new Thickness(0, 0, 3, 0)
|
||||
});
|
||||
_cpuText = new TextBlock { Text = "0%", FontSize = 11, Foreground = secondaryText, VerticalAlignment = VerticalAlignment.Center, Width = 28, TextAlignment = TextAlignment.Right };
|
||||
cpuPanel.Children.Add(_cpuText);
|
||||
DockContent.Children.Add(cpuPanel);
|
||||
needTimer = true;
|
||||
if (_cpuCounter == null)
|
||||
try { _cpuCounter = new PerformanceCounter("Processor", "% Processor Time", "_Total"); _cpuCounter.NextValue(); } catch (Exception) { }
|
||||
break;
|
||||
case "ram":
|
||||
var ramPanel = new StackPanel { Orientation = Orientation.Horizontal, VerticalAlignment = VerticalAlignment.Center };
|
||||
ramPanel.Children.Add(new TextBlock
|
||||
{
|
||||
Text = "\uE7F4", FontFamily = new FontFamily("Segoe MDL2 Assets"),
|
||||
FontSize = 11, Foreground = accentBrush,
|
||||
VerticalAlignment = VerticalAlignment.Center, Margin = new Thickness(0, 0, 3, 0)
|
||||
});
|
||||
_ramText = new TextBlock { Text = "0%", FontSize = 11, Foreground = secondaryText, VerticalAlignment = VerticalAlignment.Center, Width = 28, TextAlignment = TextAlignment.Right };
|
||||
ramPanel.Children.Add(_ramText);
|
||||
DockContent.Children.Add(ramPanel);
|
||||
needTimer = true;
|
||||
break;
|
||||
case "quickinput":
|
||||
var inputBorder = new Border
|
||||
{
|
||||
Background = TryFindResource("ItemBackground") as Brush ?? Brushes.Transparent,
|
||||
CornerRadius = new CornerRadius(8), Padding = new Thickness(8, 3, 8, 3),
|
||||
VerticalAlignment = VerticalAlignment.Center,
|
||||
};
|
||||
var inputPanel = new StackPanel { Orientation = Orientation.Horizontal };
|
||||
inputPanel.Children.Add(new TextBlock
|
||||
{
|
||||
Text = "\uE721", FontFamily = new FontFamily("Segoe MDL2 Assets"),
|
||||
FontSize = 11, Foreground = accentBrush,
|
||||
VerticalAlignment = VerticalAlignment.Center, Margin = new Thickness(0, 0, 5, 0)
|
||||
});
|
||||
_quickInput = new TextBox
|
||||
{
|
||||
Width = 100, FontSize = 11,
|
||||
Foreground = primaryText,
|
||||
CaretBrush = accentBrush,
|
||||
Background = Brushes.Transparent, BorderThickness = new Thickness(0),
|
||||
VerticalAlignment = VerticalAlignment.Center,
|
||||
};
|
||||
_quickInput.KeyDown += QuickInput_KeyDown;
|
||||
inputPanel.Children.Add(_quickInput);
|
||||
inputBorder.Child = inputPanel;
|
||||
DockContent.Children.Add(inputBorder);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (needTimer)
|
||||
{
|
||||
_timer ??= new DispatcherTimer { Interval = TimeSpan.FromSeconds(1) };
|
||||
_timer.Tick -= OnTick;
|
||||
_timer.Tick += OnTick;
|
||||
_timer.Start();
|
||||
OnTick(null, EventArgs.Empty);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>사용 가능한 모든 아이템 키와 라벨 목록을 반환합니다.</summary>
|
||||
public static IReadOnlyList<(string Key, string Icon, string Tooltip)> AvailableItems => AllItems;
|
||||
|
||||
private void AddButton(string icon, string tooltip, Brush foreground, Action click)
|
||||
{
|
||||
var border = new Border
|
||||
{
|
||||
Width = 30, Height = 30,
|
||||
CornerRadius = new CornerRadius(8),
|
||||
Background = new SolidColorBrush(Color.FromArgb(0x01, 0xFF, 0xFF, 0xFF)), // 거의 투명하지만 히트 테스트 가능
|
||||
Cursor = Cursors.Hand,
|
||||
ToolTip = tooltip,
|
||||
Margin = new Thickness(2, 0, 2, 0),
|
||||
};
|
||||
border.Child = new TextBlock
|
||||
{
|
||||
Text = icon,
|
||||
FontFamily = new FontFamily("Segoe MDL2 Assets"),
|
||||
FontSize = 14, Foreground = foreground,
|
||||
HorizontalAlignment = HorizontalAlignment.Center,
|
||||
VerticalAlignment = VerticalAlignment.Center,
|
||||
IsHitTestVisible = false, // 텍스트가 이벤트를 가로채지 않도록
|
||||
};
|
||||
border.MouseEnter += (s, _) => { if (s is Border b) b.Background = new SolidColorBrush(Color.FromArgb(0x22, 0xFF, 0xFF, 0xFF)); };
|
||||
border.MouseLeave += (s, _) => { if (s is Border b) b.Background = new SolidColorBrush(Color.FromArgb(0x01, 0xFF, 0xFF, 0xFF)); };
|
||||
border.MouseLeftButtonDown += (_, e) => { e.Handled = true; click(); };
|
||||
DockContent.Children.Add(border);
|
||||
}
|
||||
|
||||
private void AddSeparator()
|
||||
{
|
||||
DockContent.Children.Add(new Border
|
||||
{
|
||||
Width = 1, Height = 20, Margin = new Thickness(6, 0, 6, 0),
|
||||
Background = TryFindResource("SeparatorColor") as Brush ?? Brushes.Gray,
|
||||
});
|
||||
}
|
||||
|
||||
private void PositionDock()
|
||||
{
|
||||
var screen = SystemParameters.WorkArea;
|
||||
Left = (screen.Width - ActualWidth) / 2 + screen.Left;
|
||||
Top = screen.Bottom - ActualHeight - 8;
|
||||
}
|
||||
|
||||
private void OnTick(object? sender, EventArgs e)
|
||||
{
|
||||
if (_clockText != null)
|
||||
_clockText.Text = DateTime.Now.ToString("HH:mm");
|
||||
|
||||
if (_cpuText != null)
|
||||
{
|
||||
try { _cpuText.Text = $"{_cpuCounter?.NextValue() ?? 0:F0}%"; }
|
||||
catch (Exception) { _cpuText.Text = "-"; }
|
||||
}
|
||||
|
||||
if (_ramText != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
var m = new MEMORYSTATUSEX { dwLength = (uint)Marshal.SizeOf<MEMORYSTATUSEX>() };
|
||||
_ramText.Text = GlobalMemoryStatusEx(ref m) ? $"{m.dwMemoryLoad}%" : "-";
|
||||
}
|
||||
catch (Exception) { _ramText.Text = "-"; }
|
||||
}
|
||||
}
|
||||
|
||||
[DllImport("kernel32.dll")] private static extern bool GlobalMemoryStatusEx(ref MEMORYSTATUSEX lpBuffer);
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
private struct MEMORYSTATUSEX
|
||||
{
|
||||
public uint dwLength; public uint dwMemoryLoad;
|
||||
public ulong ullTotalPhys, ullAvailPhys, ullTotalPageFile, ullAvailPageFile, ullTotalVirtual, ullAvailVirtual, ullAvailExtendedVirtual;
|
||||
}
|
||||
|
||||
private void QuickInput_KeyDown(object sender, KeyEventArgs e)
|
||||
{
|
||||
if (e.Key == Key.Enter && _quickInput != null && !string.IsNullOrWhiteSpace(_quickInput.Text))
|
||||
{
|
||||
OnQuickSearch?.Invoke(_quickInput.Text);
|
||||
_quickInput.Text = "";
|
||||
e.Handled = true;
|
||||
}
|
||||
else if (e.Key == Key.Escape && _quickInput != null)
|
||||
{
|
||||
_quickInput.Text = "";
|
||||
e.Handled = true;
|
||||
}
|
||||
}
|
||||
|
||||
private void Window_KeyDown(object sender, KeyEventArgs e)
|
||||
{
|
||||
if (e.Key == Key.Escape) Hide();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user