[Phase L2-8] DockBar 위젯 확장 — 날씨·일정·배터리 추가
Services/WeatherWidgetService.cs (신규 58줄): - wttr.in API 호출, 30분 캐시 - 사내 모드(InternalModeEnabled=true)에서는 "--" 즉시 반환 - Invalidate()로 강제 캐시 초기화 지원 ViewModels/LauncherViewModel.Widgets.cs: - Widget_WeatherText (setter: 코드비하인드에서 직접 갱신) - Widget_CalText: DateTime.Now → "M/d (ddd)" 형식 (ko-KR) - Widget_BatteryText / Widget_BatteryIcon / Widget_BatteryVisible Views/LauncherWindow.Widgets.cs: - StartWidgetUpdates(): 배터리 즉시 갱신 + 날씨 비동기 갱신 호출 - 1초 타이머: 배터리 30초마다, 날씨 캐시체크 2분마다 - UpdateBatteryWidget(): PowerStatus 읽기, 잔량별 MDL2 아이콘 선택 - RefreshWeatherAsync(): WeatherWidgetService 호출 → VM 프로퍼티 갱신 - WgtWeather_Click: 사외 모드=wttr.in 열기, 사내 모드=캐시 초기화 - WgtCal_Click: ms-clock: 또는 outlookcal: 열기 - WgtBattery_Click: ms-settings:powersleep 열기 Views/LauncherWindow.xaml: - WidgetBar 내부: Grid → StackPanel + 2행 구조로 변환 - Row A: 기존 4개 위젯 (시스템·포모·메모·서버, 변경 없음) - Row B: E(날씨 파랑) · F(일정 핑크) · G(배터리 녹색) 신규 추가 - 배터리: BoolToVisibilityConverter로 데스크톱에서 자동 숨김 빌드: 경고 0, 오류 0 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
63
src/AxCopilot/Services/WeatherWidgetService.cs
Normal file
63
src/AxCopilot/Services/WeatherWidgetService.cs
Normal file
@@ -0,0 +1,63 @@
|
||||
using System.Net.Http;
|
||||
|
||||
namespace AxCopilot.Services;
|
||||
|
||||
/// <summary>
|
||||
/// 날씨 위젯용 데이터 서비스.
|
||||
/// 사외 모드에서만 외부 API(wttr.in)를 호출하며 30분간 결과를 캐시합니다.
|
||||
/// 사내 모드(InternalModeEnabled=true)에서는 "--"를 즉시 반환합니다.
|
||||
/// </summary>
|
||||
internal static class WeatherWidgetService
|
||||
{
|
||||
private static string? _cached;
|
||||
private static DateTime _cacheTime = DateTime.MinValue;
|
||||
private static bool _fetching;
|
||||
private static readonly TimeSpan _ttl = TimeSpan.FromMinutes(30);
|
||||
|
||||
/// <summary>현재 캐시된 날씨 텍스트 (없으면 "--")</summary>
|
||||
public static string CachedText => _cached ?? "--";
|
||||
|
||||
/// <summary>
|
||||
/// 날씨 정보를 비동기로 갱신합니다.
|
||||
/// 사내 모드이거나 캐시 유효 기간 내이면 즉시 반환합니다.
|
||||
/// </summary>
|
||||
public static async Task RefreshAsync(bool internalMode)
|
||||
{
|
||||
if (internalMode)
|
||||
{
|
||||
_cached = "--";
|
||||
return;
|
||||
}
|
||||
|
||||
// 캐시 유효하면 재호출 불필요
|
||||
if (_cached != null && DateTime.Now - _cacheTime < _ttl) return;
|
||||
if (_fetching) return;
|
||||
|
||||
_fetching = true;
|
||||
try
|
||||
{
|
||||
using var client = new HttpClient();
|
||||
client.Timeout = TimeSpan.FromSeconds(6);
|
||||
// wttr.in: %c = 날씨 조건 이모지, %t = 기온
|
||||
var raw = await client.GetStringAsync("https://wttr.in/?format=%c+%t");
|
||||
_cached = raw.Trim().Replace("+", " ");
|
||||
_cacheTime = DateTime.Now;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogService.Warn($"날씨 위젯 갱신 실패: {ex.Message}");
|
||||
_cached ??= "--";
|
||||
}
|
||||
finally
|
||||
{
|
||||
_fetching = false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>캐시를 강제 초기화하여 다음 호출 시 재조회합니다.</summary>
|
||||
public static void Invalidate()
|
||||
{
|
||||
_cached = null;
|
||||
_cacheTime = DateTime.MinValue;
|
||||
}
|
||||
}
|
||||
@@ -57,6 +57,51 @@ public partial class LauncherViewModel
|
||||
public bool Widget_McpOnline => ServerStatusService.Instance.McpOnline;
|
||||
public string Widget_McpName => ServerStatusService.Instance.McpName;
|
||||
|
||||
// ─── 날씨 ─────────────────────────────────────────────────────────────────
|
||||
|
||||
private string _widget_WeatherText = "--";
|
||||
|
||||
/// <summary>날씨 위젯 텍스트 예: "⛅ 18°C" (사외 모드) / "--" (사내 모드)</summary>
|
||||
public string Widget_WeatherText
|
||||
{
|
||||
get => _widget_WeatherText;
|
||||
internal set { _widget_WeatherText = value; OnPropertyChanged(); }
|
||||
}
|
||||
|
||||
// ─── 날짜/일정 ────────────────────────────────────────────────────────────
|
||||
|
||||
/// <summary>날짜 위젯 텍스트 예: "4/4 (화)"</summary>
|
||||
public string Widget_CalText =>
|
||||
DateTime.Now.ToString("M/d (ddd)",
|
||||
System.Globalization.CultureInfo.GetCultureInfo("ko-KR"));
|
||||
|
||||
// ─── 배터리 ───────────────────────────────────────────────────────────────
|
||||
|
||||
private string _widget_BatteryText = "--";
|
||||
private string _widget_BatteryIcon = "\uE83F";
|
||||
private bool _widget_BatteryVisible = false;
|
||||
|
||||
/// <summary>배터리 퍼센트 및 충전 표시. 예: "87%" / "45% ⚡"</summary>
|
||||
public string Widget_BatteryText
|
||||
{
|
||||
get => _widget_BatteryText;
|
||||
internal set { _widget_BatteryText = value; OnPropertyChanged(); }
|
||||
}
|
||||
|
||||
/// <summary>배터리 잔량별 Segoe MDL2 아이콘 코드포인트</summary>
|
||||
public string Widget_BatteryIcon
|
||||
{
|
||||
get => _widget_BatteryIcon;
|
||||
internal set { _widget_BatteryIcon = value; OnPropertyChanged(); }
|
||||
}
|
||||
|
||||
/// <summary>배터리 위젯 표시 여부 (데스크톱/배터리 없음이면 false)</summary>
|
||||
public bool Widget_BatteryVisible
|
||||
{
|
||||
get => _widget_BatteryVisible;
|
||||
internal set { _widget_BatteryVisible = value; OnPropertyChanged(); }
|
||||
}
|
||||
|
||||
// ─── 갱신 메서드 ──────────────────────────────────────────────────────────
|
||||
|
||||
/// <summary>1초마다 LauncherWindow.Widgets.cs에서 호출 — UI 바인딩 갱신.</summary>
|
||||
@@ -75,6 +120,9 @@ public partial class LauncherViewModel
|
||||
OnPropertyChanged(nameof(Widget_LlmOnline));
|
||||
OnPropertyChanged(nameof(Widget_McpOnline));
|
||||
OnPropertyChanged(nameof(Widget_McpName));
|
||||
OnPropertyChanged(nameof(Widget_CalText));
|
||||
// WeatherText / BatteryText / BatteryIcon / BatteryVisible 는
|
||||
// LauncherWindow.Widgets.cs에서 직접 setter 호출로 갱신
|
||||
}
|
||||
|
||||
private int _widgetRefreshTick;
|
||||
|
||||
@@ -45,6 +45,9 @@ public partial class LauncherWindow
|
||||
// 메모 건수 즉시 갱신 (최초 1회)
|
||||
_vm.UpdateWidgets();
|
||||
UpdateServerDots();
|
||||
UpdateBatteryWidget();
|
||||
// 날씨: 사외 모드에서만 비동기 갱신
|
||||
_ = RefreshWeatherAsync();
|
||||
|
||||
// 1초 타이머
|
||||
if (_widgetTimer == null)
|
||||
@@ -57,6 +60,14 @@ public partial class LauncherWindow
|
||||
{
|
||||
_vm.UpdateWidgets();
|
||||
UpdateServerDots();
|
||||
|
||||
// 배터리: 30초마다 갱신
|
||||
if (_vm.Widget_PerfText.Length > 0 && _widgetBatteryTick++ % 30 == 0)
|
||||
UpdateBatteryWidget();
|
||||
|
||||
// 날씨: 2분마다 서비스 쪽 캐시를 체크 (실제 API 호출은 30분마다)
|
||||
if (_widgetWeatherTick++ % 120 == 0)
|
||||
_ = RefreshWeatherAsync();
|
||||
};
|
||||
}
|
||||
_widgetTimer.Start();
|
||||
@@ -65,6 +76,9 @@ public partial class LauncherWindow
|
||||
UpdatePomoWidgetStyle();
|
||||
}
|
||||
|
||||
private int _widgetBatteryTick;
|
||||
private int _widgetWeatherTick;
|
||||
|
||||
/// <summary>런처가 숨겨질 때 타이머 중지 (뽀모도로는 계속 실행).</summary>
|
||||
internal void StopWidgetUpdates()
|
||||
{
|
||||
@@ -140,4 +154,112 @@ public partial class LauncherWindow
|
||||
_vm.InputText = "port";
|
||||
InputBox?.Focus();
|
||||
}
|
||||
|
||||
private void WgtWeather_Click(object sender, System.Windows.Input.MouseButtonEventArgs e)
|
||||
{
|
||||
var internalMode = CurrentApp?.SettingsService?.Settings.InternalModeEnabled ?? true;
|
||||
if (!internalMode)
|
||||
{
|
||||
// 사외 모드: 브라우저에서 날씨 페이지 열기
|
||||
try
|
||||
{
|
||||
System.Diagnostics.Process.Start(new System.Diagnostics.ProcessStartInfo(
|
||||
"https://wttr.in") { UseShellExecute = true });
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogService.Warn($"날씨 링크 열기 실패: {ex.Message}");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// 사내 모드: 날씨 캐시 무효화 후 재시도 (사외 모드 전환 시 즉시 갱신되도록)
|
||||
WeatherWidgetService.Invalidate();
|
||||
_ = RefreshWeatherAsync();
|
||||
}
|
||||
}
|
||||
|
||||
private void WgtCal_Click(object sender, System.Windows.Input.MouseButtonEventArgs e)
|
||||
{
|
||||
// Windows 달력 앱 열기
|
||||
try
|
||||
{
|
||||
System.Diagnostics.Process.Start(new System.Diagnostics.ProcessStartInfo(
|
||||
"ms-clock:") { UseShellExecute = true });
|
||||
}
|
||||
catch
|
||||
{
|
||||
try
|
||||
{
|
||||
System.Diagnostics.Process.Start(new System.Diagnostics.ProcessStartInfo(
|
||||
"outlookcal:") { UseShellExecute = true });
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogService.Warn($"달력 앱 열기 실패: {ex.Message}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void WgtBattery_Click(object sender, System.Windows.Input.MouseButtonEventArgs e)
|
||||
{
|
||||
// 전원 및 절전 설정 열기
|
||||
try
|
||||
{
|
||||
System.Diagnostics.Process.Start(new System.Diagnostics.ProcessStartInfo(
|
||||
"ms-settings:powersleep") { UseShellExecute = true });
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogService.Warn($"전원 설정 열기 실패: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
// ─── 배터리 상태 갱신 ──────────────────────────────────────────────────────
|
||||
|
||||
private void UpdateBatteryWidget()
|
||||
{
|
||||
try
|
||||
{
|
||||
var ps = System.Windows.Forms.SystemInformation.PowerStatus;
|
||||
var pct = ps.BatteryLifePercent; // 0.0~1.0, unknown = 255f
|
||||
|
||||
if (pct > 1.0f || pct < 0f)
|
||||
{
|
||||
// 배터리 없는 장치 (데스크톱 등) → 위젯 숨김
|
||||
_vm.Widget_BatteryVisible = false;
|
||||
return;
|
||||
}
|
||||
|
||||
_vm.Widget_BatteryVisible = true;
|
||||
var pctInt = (int)(pct * 100);
|
||||
var charging = ps.PowerLineStatus == System.Windows.Forms.PowerLineStatus.Online;
|
||||
|
||||
_vm.Widget_BatteryText = charging ? $"{pctInt}% ⚡" : $"{pctInt}%";
|
||||
_vm.Widget_BatteryIcon = charging ? "\uE83E" // BatteryCharging
|
||||
: pctInt >= 85 ? "\uEBA7" // Battery 8 (Full)
|
||||
: pctInt >= 70 ? "\uEBA5" // Battery 6
|
||||
: pctInt >= 50 ? "\uEBA3" // Battery 4
|
||||
: pctInt >= 25 ? "\uEBA1" // Battery 2
|
||||
: "\uEBA0"; // Battery 1 (Low)
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogService.Warn($"배터리 위젯 갱신 실패: {ex.Message}");
|
||||
_vm.Widget_BatteryVisible = false;
|
||||
}
|
||||
}
|
||||
|
||||
// ─── 날씨 비동기 갱신 ─────────────────────────────────────────────────────
|
||||
|
||||
private async Task RefreshWeatherAsync()
|
||||
{
|
||||
var internalMode = CurrentApp?.SettingsService?.Settings.InternalModeEnabled ?? true;
|
||||
await WeatherWidgetService.RefreshAsync(internalMode);
|
||||
// UI 스레드에서 바인딩 프로퍼티 갱신
|
||||
await Dispatcher.InvokeAsync(() =>
|
||||
{
|
||||
_vm.Widget_WeatherText = WeatherWidgetService.CachedText;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -790,116 +790,189 @@
|
||||
Margin="0,0,0,8"
|
||||
Opacity="0.7"/>
|
||||
|
||||
<!-- ─── Phase L3-9: 미니 위젯 바 ─── -->
|
||||
<!-- ─── 위젯 바 (Row A: 기존 4개 / Row B: 날씨·일정·배터리) ─── -->
|
||||
<Border x:Name="WidgetBar"
|
||||
Grid.Row="6"
|
||||
BorderBrush="{DynamicResource SeparatorColor}"
|
||||
BorderThickness="0,1,0,0"
|
||||
Padding="10,7,10,9">
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*"/>
|
||||
<ColumnDefinition Width="6"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="6"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="6"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<StackPanel>
|
||||
|
||||
<!-- A: 시스템 모니터 -->
|
||||
<Border x:Name="WgtPerf" Grid.Column="0"
|
||||
CornerRadius="5" Padding="8,5"
|
||||
Background="#0D60A5FA"
|
||||
Cursor="Hand"
|
||||
MouseLeftButtonUp="WgtPerf_Click">
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<TextBlock Text=""
|
||||
FontFamily="Segoe MDL2 Assets" FontSize="10"
|
||||
Foreground="#60A5FA"
|
||||
VerticalAlignment="Center" Margin="0,0,5,0"/>
|
||||
<TextBlock Text="{Binding Widget_PerfText}"
|
||||
FontSize="10"
|
||||
Foreground="{DynamicResource SecondaryText}"
|
||||
VerticalAlignment="Center"/>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
<!-- ── Row A: 시스템·포모·메모·서버 ── -->
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*"/>
|
||||
<ColumnDefinition Width="6"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="6"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="6"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<!-- B: 뽀모도로 타이머 -->
|
||||
<Border x:Name="WgtPomo" Grid.Column="2"
|
||||
CornerRadius="5" Padding="8,5"
|
||||
Background="#0DF59E0B"
|
||||
Cursor="Hand"
|
||||
MouseLeftButtonUp="WgtPomo_Click">
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<TextBlock Text=""
|
||||
FontFamily="Segoe MDL2 Assets" FontSize="10"
|
||||
Foreground="#F59E0B"
|
||||
VerticalAlignment="Center" Margin="0,0,5,0"/>
|
||||
<TextBlock x:Name="WgtPomoText"
|
||||
Text="{Binding Widget_PomoText}"
|
||||
FontSize="10"
|
||||
Foreground="{DynamicResource SecondaryText}"
|
||||
VerticalAlignment="Center"/>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
<!-- A: 시스템 모니터 -->
|
||||
<Border x:Name="WgtPerf" Grid.Column="0"
|
||||
CornerRadius="5" Padding="8,5"
|
||||
Background="#0D60A5FA"
|
||||
Cursor="Hand"
|
||||
MouseLeftButtonUp="WgtPerf_Click">
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<TextBlock Text=""
|
||||
FontFamily="Segoe MDL2 Assets" FontSize="10"
|
||||
Foreground="#60A5FA"
|
||||
VerticalAlignment="Center" Margin="0,0,5,0"/>
|
||||
<TextBlock Text="{Binding Widget_PerfText}"
|
||||
FontSize="10"
|
||||
Foreground="{DynamicResource SecondaryText}"
|
||||
VerticalAlignment="Center"/>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
|
||||
<!-- C: 빠른 메모 -->
|
||||
<Border x:Name="WgtNote" Grid.Column="4"
|
||||
CornerRadius="5" Padding="8,5"
|
||||
Background="#0D8B5CF6"
|
||||
Cursor="Hand"
|
||||
MouseLeftButtonUp="WgtNote_Click">
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<TextBlock Text=""
|
||||
FontFamily="Segoe MDL2 Assets" FontSize="10"
|
||||
Foreground="#8B5CF6"
|
||||
VerticalAlignment="Center" Margin="0,0,5,0"/>
|
||||
<TextBlock Text="{Binding Widget_NoteText}"
|
||||
FontSize="10"
|
||||
Foreground="{DynamicResource SecondaryText}"
|
||||
VerticalAlignment="Center"/>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
<!-- B: 뽀모도로 타이머 -->
|
||||
<Border x:Name="WgtPomo" Grid.Column="2"
|
||||
CornerRadius="5" Padding="8,5"
|
||||
Background="#0DF59E0B"
|
||||
Cursor="Hand"
|
||||
MouseLeftButtonUp="WgtPomo_Click">
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<TextBlock Text=""
|
||||
FontFamily="Segoe MDL2 Assets" FontSize="10"
|
||||
Foreground="#F59E0B"
|
||||
VerticalAlignment="Center" Margin="0,0,5,0"/>
|
||||
<TextBlock x:Name="WgtPomoText"
|
||||
Text="{Binding Widget_PomoText}"
|
||||
FontSize="10"
|
||||
Foreground="{DynamicResource SecondaryText}"
|
||||
VerticalAlignment="Center"/>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
|
||||
<!-- D: 서버 상태 -->
|
||||
<Border x:Name="WgtServer" Grid.Column="6"
|
||||
CornerRadius="5" Padding="8,5"
|
||||
Background="#0D10B981"
|
||||
Cursor="Hand"
|
||||
MouseLeftButtonUp="WgtServer_Click">
|
||||
<StackPanel Orientation="Horizontal" x:Name="WgtServerContent">
|
||||
<!-- Ollama 상태 -->
|
||||
<Ellipse x:Name="OllamaStatusDot"
|
||||
Width="6" Height="6"
|
||||
Fill="#9E9E9E"
|
||||
VerticalAlignment="Center" Margin="0,0,3,0"/>
|
||||
<TextBlock Text="Ollama"
|
||||
FontSize="10"
|
||||
Foreground="{DynamicResource SecondaryText}"
|
||||
VerticalAlignment="Center" Margin="0,0,8,0"/>
|
||||
<!-- LLM API 상태 -->
|
||||
<Ellipse x:Name="LlmStatusDot"
|
||||
Width="6" Height="6"
|
||||
Fill="#9E9E9E"
|
||||
VerticalAlignment="Center" Margin="0,0,3,0"/>
|
||||
<TextBlock Text="API"
|
||||
FontSize="10"
|
||||
Foreground="{DynamicResource SecondaryText}"
|
||||
VerticalAlignment="Center" Margin="0,0,8,0"/>
|
||||
<!-- MCP 상태 -->
|
||||
<Ellipse x:Name="McpStatusDot"
|
||||
Width="6" Height="6"
|
||||
Fill="#9E9E9E"
|
||||
VerticalAlignment="Center" Margin="0,0,3,0"/>
|
||||
<TextBlock x:Name="McpNameText"
|
||||
Text="{Binding Widget_McpName}"
|
||||
FontSize="10"
|
||||
Foreground="{DynamicResource SecondaryText}"
|
||||
VerticalAlignment="Center"/>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
</Grid>
|
||||
<!-- C: 빠른 메모 -->
|
||||
<Border x:Name="WgtNote" Grid.Column="4"
|
||||
CornerRadius="5" Padding="8,5"
|
||||
Background="#0D8B5CF6"
|
||||
Cursor="Hand"
|
||||
MouseLeftButtonUp="WgtNote_Click">
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<TextBlock Text=""
|
||||
FontFamily="Segoe MDL2 Assets" FontSize="10"
|
||||
Foreground="#8B5CF6"
|
||||
VerticalAlignment="Center" Margin="0,0,5,0"/>
|
||||
<TextBlock Text="{Binding Widget_NoteText}"
|
||||
FontSize="10"
|
||||
Foreground="{DynamicResource SecondaryText}"
|
||||
VerticalAlignment="Center"/>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
|
||||
<!-- D: 서버 상태 -->
|
||||
<Border x:Name="WgtServer" Grid.Column="6"
|
||||
CornerRadius="5" Padding="8,5"
|
||||
Background="#0D10B981"
|
||||
Cursor="Hand"
|
||||
MouseLeftButtonUp="WgtServer_Click">
|
||||
<StackPanel Orientation="Horizontal" x:Name="WgtServerContent">
|
||||
<!-- Ollama 상태 -->
|
||||
<Ellipse x:Name="OllamaStatusDot"
|
||||
Width="6" Height="6"
|
||||
Fill="#9E9E9E"
|
||||
VerticalAlignment="Center" Margin="0,0,3,0"/>
|
||||
<TextBlock Text="Ollama"
|
||||
FontSize="10"
|
||||
Foreground="{DynamicResource SecondaryText}"
|
||||
VerticalAlignment="Center" Margin="0,0,8,0"/>
|
||||
<!-- LLM API 상태 -->
|
||||
<Ellipse x:Name="LlmStatusDot"
|
||||
Width="6" Height="6"
|
||||
Fill="#9E9E9E"
|
||||
VerticalAlignment="Center" Margin="0,0,3,0"/>
|
||||
<TextBlock Text="API"
|
||||
FontSize="10"
|
||||
Foreground="{DynamicResource SecondaryText}"
|
||||
VerticalAlignment="Center" Margin="0,0,8,0"/>
|
||||
<!-- MCP 상태 -->
|
||||
<Ellipse x:Name="McpStatusDot"
|
||||
Width="6" Height="6"
|
||||
Fill="#9E9E9E"
|
||||
VerticalAlignment="Center" Margin="0,0,3,0"/>
|
||||
<TextBlock x:Name="McpNameText"
|
||||
Text="{Binding Widget_McpName}"
|
||||
FontSize="10"
|
||||
Foreground="{DynamicResource SecondaryText}"
|
||||
VerticalAlignment="Center"/>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
</Grid>
|
||||
|
||||
<!-- ── Row B: 날씨·일정·배터리 ── -->
|
||||
<Grid Margin="0,4,0,0">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*"/>
|
||||
<ColumnDefinition Width="6"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
<ColumnDefinition Width="6"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<!-- E: 날씨 -->
|
||||
<Border x:Name="WgtWeather" Grid.Column="0"
|
||||
CornerRadius="5" Padding="8,5"
|
||||
Background="#0D3B82F6"
|
||||
Cursor="Hand"
|
||||
MouseLeftButtonUp="WgtWeather_Click">
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<TextBlock Text=""
|
||||
FontFamily="Segoe MDL2 Assets" FontSize="10"
|
||||
Foreground="#60A5FA"
|
||||
VerticalAlignment="Center" Margin="0,0,5,0"/>
|
||||
<TextBlock Text="{Binding Widget_WeatherText}"
|
||||
FontSize="10"
|
||||
Foreground="{DynamicResource SecondaryText}"
|
||||
VerticalAlignment="Center"
|
||||
TextTrimming="CharacterEllipsis"
|
||||
MaxWidth="100"/>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
|
||||
<!-- F: 날짜/일정 -->
|
||||
<Border x:Name="WgtCal" Grid.Column="2"
|
||||
CornerRadius="5" Padding="8,5"
|
||||
Background="#0DEC4899"
|
||||
Cursor="Hand"
|
||||
MouseLeftButtonUp="WgtCal_Click">
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<TextBlock Text=""
|
||||
FontFamily="Segoe MDL2 Assets" FontSize="10"
|
||||
Foreground="#EC4899"
|
||||
VerticalAlignment="Center" Margin="0,0,5,0"/>
|
||||
<TextBlock Text="{Binding Widget_CalText}"
|
||||
FontSize="10"
|
||||
Foreground="{DynamicResource SecondaryText}"
|
||||
VerticalAlignment="Center"/>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
|
||||
<!-- G: 배터리 -->
|
||||
<Border x:Name="WgtBattery" Grid.Column="4"
|
||||
CornerRadius="5" Padding="8,5"
|
||||
Background="#0D10B981"
|
||||
Cursor="Hand"
|
||||
MouseLeftButtonUp="WgtBattery_Click"
|
||||
Visibility="{Binding Widget_BatteryVisible, Converter={StaticResource BoolToVisibilityConverter}}">
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<TextBlock Text="{Binding Widget_BatteryIcon}"
|
||||
FontFamily="Segoe MDL2 Assets" FontSize="10"
|
||||
Foreground="#10B981"
|
||||
VerticalAlignment="Center" Margin="0,0,5,0"/>
|
||||
<TextBlock Text="{Binding Widget_BatteryText}"
|
||||
FontSize="10"
|
||||
Foreground="{DynamicResource SecondaryText}"
|
||||
VerticalAlignment="Center"/>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
</Grid>
|
||||
|
||||
</StackPanel>
|
||||
</Border>
|
||||
|
||||
<!-- ─── 토스트 오버레이 ─── -->
|
||||
|
||||
Reference in New Issue
Block a user