diff --git a/crates/prism-client/src/config/servers.rs b/crates/prism-client/src/config/servers.rs index 3b5f333..b2d9ff9 100644 --- a/crates/prism-client/src/config/servers.rs +++ b/crates/prism-client/src/config/servers.rs @@ -59,6 +59,19 @@ fn hsl_to_rgb(h: f64, s: f64, l: f64) -> [u8; 3] { ] } +// --------------------------------------------------------------------------- +// ServerStatus +// --------------------------------------------------------------------------- + +/// Derived connection status for UI rendering. +/// TODO: Replace heuristic derivation with runtime discovery/ping probe. +#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)] +pub enum ServerStatus { + Online, + Sleeping, + Unreachable, +} + // --------------------------------------------------------------------------- // SavedServer // --------------------------------------------------------------------------- @@ -75,6 +88,14 @@ pub struct SavedServer { pub last_resolution: Option<(u32, u32)>, pub last_codec: Option, pub created_at: u64, + #[serde(default)] + pub os_label: Option, + #[serde(default)] + pub tags: Vec, + #[serde(default)] + pub wol_supported: bool, + #[serde(default)] + pub last_latency_ms: Option, } impl SavedServer { @@ -98,6 +119,33 @@ impl SavedServer { last_resolution: None, last_codec: None, created_at, + os_label: None, + tags: Vec::new(), + wol_supported: false, + last_latency_ms: None, + } + } + + /// Heuristic status until real-time probing is implemented. + pub fn derived_status(&self) -> ServerStatus { + const SECS_6H: u64 = 6 * 60 * 60; + const SECS_7D: u64 = 7 * 24 * 60 * 60; + match self.last_connected { + None => ServerStatus::Unreachable, + Some(epoch_secs) => { + let now = SystemTime::now() + .duration_since(UNIX_EPOCH) + .unwrap_or_default() + .as_secs(); + let age = now.saturating_sub(epoch_secs); + if age < SECS_6H { + ServerStatus::Online + } else if age < SECS_7D { + ServerStatus::Sleeping + } else { + ServerStatus::Unreachable + } + } } } } diff --git a/crates/prism-client/src/ui/launcher/card_grid.rs b/crates/prism-client/src/ui/launcher/card_grid.rs index de3829e..5469f00 100644 --- a/crates/prism-client/src/ui/launcher/card_grid.rs +++ b/crates/prism-client/src/ui/launcher/card_grid.rs @@ -137,7 +137,7 @@ impl CardGrid { .cards .iter() .enumerate() - .filter_map(|(index, card)| card.matches_filter(self.active_filter).then_some(index)) + .filter_map(|(index, card)| card.matches_filter(&self.active_filter).then_some(index)) .collect(); } @@ -155,7 +155,7 @@ impl CardGrid { CardFilter::Dormant, CardFilter::New, ] { - let label = filter.label(self.cards.len()); + let label = filter.label(); let w = theme::text_width(&label, 11.0) + 28.0; let rect = Rect::new(x, y, w, FILTER_H); self.filter_chip_rects.push((filter, rect)); @@ -292,8 +292,8 @@ impl Widget for CardGrid { fn paint(&self, ctx: &mut PaintContext) { if self.show_filters { for (filter, rect) in &self.filter_chip_rects { - let active = *filter == self.active_filter; - let hovered = self.hovered_filter == Some(*filter); + let active = filter == &self.active_filter; + let hovered = self.hovered_filter.as_ref() == Some(filter); let pill_radius = 16.0; if active { @@ -312,10 +312,10 @@ impl Widget for CardGrid { ctx.push_text_run(TextRun { x: rect.x + 14.0, y: rect.y + 10.0, - text: filter.label(self.cards.len()), + text: filter.label(), font_size: 11.0, color: [1.0, 1.0, 1.0, 1.0], - monospace: false, + ..Default::default() }); } else { // Light frosted pill for inactive @@ -331,10 +331,10 @@ impl Widget for CardGrid { ctx.push_text_run(TextRun { x: rect.x + 14.0, y: rect.y + 10.0, - text: filter.label(self.cards.len()), + text: filter.label(), font_size: 11.0, color: theme::LT_TEXT_SECONDARY, - monospace: false, + ..Default::default() }); } } @@ -352,7 +352,7 @@ impl Widget for CardGrid { text: "No saved desktops match this filter.".to_string(), font_size: 12.0, color: theme::LT_TEXT_MUTED, - monospace: false, + ..Default::default() }); } @@ -398,7 +398,7 @@ impl Widget for CardGrid { text: plus.to_string(), font_size: 28.0, color: theme::PRIMARY_BLUE, - monospace: false, + ..Default::default() }); let title = "Add New Connection"; @@ -408,7 +408,7 @@ impl Widget for CardGrid { text: title.to_string(), font_size: 14.0, color: theme::LT_TEXT_PRIMARY, - monospace: false, + ..Default::default() }); let body = "Manual IP or Network Discovery"; @@ -418,7 +418,7 @@ impl Widget for CardGrid { text: body.to_string(), font_size: 11.0, color: theme::LT_TEXT_MUTED, - monospace: false, + ..Default::default() }); } } @@ -429,7 +429,7 @@ impl Widget for CardGrid { self.hovered_filter = self .filter_chip_rects .iter() - .find_map(|(filter, rect)| rect.contains(*x, *y).then_some(*filter)); + .find_map(|(filter, rect)| rect.contains(*x, *y).then_some(filter.clone())); } UiEvent::MouseDown { x, @@ -442,7 +442,7 @@ impl Widget for CardGrid { .find(|(_, rect)| rect.contains(*x, *y)) { if self.active_filter != *filter { - self.active_filter = *filter; + self.active_filter = filter.clone(); self.recompute_layout(); } return EventResponse::Consumed; diff --git a/crates/prism-client/src/ui/launcher/nav.rs b/crates/prism-client/src/ui/launcher/nav.rs index 300abc3..bcb6920 100644 --- a/crates/prism-client/src/ui/launcher/nav.rs +++ b/crates/prism-client/src/ui/launcher/nav.rs @@ -79,7 +79,7 @@ impl Widget for LauncherNav { text: "PRISM".into(), font_size: 20.0, color: theme::LT_TEXT_PRIMARY, - monospace: false, + ..Default::default() }); ctx.push_text_run(TextRun { x: self.rect.x + 18.0, @@ -87,7 +87,7 @@ impl Widget for LauncherNav { text: "Remote client".into(), font_size: 11.0, color: theme::LT_TEXT_MUTED, - monospace: false, + ..Default::default() }); for (tab, rect) in &self.primary_items { @@ -110,7 +110,7 @@ impl Widget for LauncherNav { } else { theme::LT_TEXT_SECONDARY }, - monospace: false, + ..Default::default() }); } @@ -132,7 +132,7 @@ impl Widget for LauncherNav { } else { theme::LT_TEXT_SECONDARY }, - monospace: false, + ..Default::default() }); } diff --git a/crates/prism-client/src/ui/launcher/profiles.rs b/crates/prism-client/src/ui/launcher/profiles.rs index d50b449..01995f9 100644 --- a/crates/prism-client/src/ui/launcher/profiles.rs +++ b/crates/prism-client/src/ui/launcher/profiles.rs @@ -418,7 +418,7 @@ impl Widget for ProfilesPanel { text: "Presets".into(), font_size: theme::FONT_LABEL, color: theme::LT_TEXT_MUTED, - monospace: false, + ..Default::default() }); for (idx, row) in self.list_rows.iter().enumerate() { @@ -451,7 +451,7 @@ impl Widget for ProfilesPanel { } else { theme::LT_TEXT_SECONDARY }, - monospace: false, + ..Default::default() }); let subtitle = if profile.builtin { @@ -474,7 +474,7 @@ impl Widget for ProfilesPanel { text: subtitle, font_size: 11.0, color: theme::LT_TEXT_MUTED, - monospace: false, + ..Default::default() }); } @@ -493,7 +493,7 @@ impl Widget for ProfilesPanel { text: draft.name.clone(), font_size: theme::FONT_HERO, color: theme::LT_TEXT_PRIMARY, - monospace: false, + ..Default::default() }); if draft.builtin { @@ -510,7 +510,7 @@ impl Widget for ProfilesPanel { text: "SYSTEM".to_string(), font_size: 10.0, color: theme::launcher_chip_text_color(theme::ChipTone::Success), - monospace: false, + ..Default::default() }); } @@ -525,7 +525,7 @@ impl Widget for ProfilesPanel { text: "UNSAVED".to_string(), font_size: 10.0, color: theme::launcher_chip_text_color(theme::ChipTone::Warning), - monospace: false, + ..Default::default() }); } @@ -535,7 +535,7 @@ impl Widget for ProfilesPanel { text: "Optimized for high-performance interaction".to_string(), font_size: theme::FONT_BODY, color: theme::LT_TEXT_SECONDARY, - monospace: false, + ..Default::default() }); } @@ -552,7 +552,7 @@ impl Widget for ProfilesPanel { text: text.to_string(), font_size: theme::FONT_CAPTION, color: theme::LT_TEXT_MUTED, - monospace: false, + ..Default::default() }); }; diff --git a/crates/prism-client/src/ui/launcher/quick_connect.rs b/crates/prism-client/src/ui/launcher/quick_connect.rs index 4326a9a..ce364c2 100644 --- a/crates/prism-client/src/ui/launcher/quick_connect.rs +++ b/crates/prism-client/src/ui/launcher/quick_connect.rs @@ -78,7 +78,7 @@ impl Widget for QuickConnect { text: title.into(), font_size: theme::FONT_HERO, color: theme::LT_TEXT_PRIMARY, - monospace: false, + ..Default::default() }); let subtitle = "Enter a hostname or IP address"; @@ -89,7 +89,7 @@ impl Widget for QuickConnect { text: subtitle.into(), font_size: theme::FONT_BODY, color: theme::LT_TEXT_MUTED, - monospace: false, + ..Default::default() }); self.address_input.paint(ctx); diff --git a/crates/prism-client/src/ui/launcher/server_card.rs b/crates/prism-client/src/ui/launcher/server_card.rs index 680ab9c..7ee11d9 100644 --- a/crates/prism-client/src/ui/launcher/server_card.rs +++ b/crates/prism-client/src/ui/launcher/server_card.rs @@ -20,21 +20,23 @@ pub enum CardLayoutMode { Row, } -#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[derive(Debug, Clone, PartialEq, Eq)] pub enum CardFilter { All, Recent, Dormant, New, + Tag(String), // e.g. "WORK", "PERSONAL" } impl CardFilter { - pub fn label(self, total_cards: usize) -> String { + pub fn label(&self) -> String { match self { - CardFilter::All => format!("All Hosts ({total_cards})"), - CardFilter::Recent => "Recent".to_string(), - CardFilter::Dormant => "Dormant".to_string(), - CardFilter::New => "New".to_string(), + Self::All => "All Hosts".into(), + Self::Recent => "Recent".into(), + Self::Dormant => "Dormant".into(), + Self::New => "New".into(), + Self::Tag(t) => t.clone(), } } } @@ -80,6 +82,7 @@ pub struct ServerCard { last_connected: Option, last_info: String, accent_color: [f32; 3], + tags: Vec, connect_button: Button, edit_button: Button, delete_button: Button, @@ -119,6 +122,7 @@ impl ServerCard { last_connected: server.last_connected, last_info, accent_color, + tags: server.tags.clone(), connect_button: Button::new( "Connect", UiAction::Connect { @@ -156,12 +160,13 @@ impl ServerCard { self.layout_mode = mode; } - pub fn matches_filter(&self, filter: CardFilter) -> bool { + pub fn matches_filter(&self, filter: &CardFilter) -> bool { match filter { CardFilter::All => true, CardFilter::Recent => self.status() == CardStatus::Recent, CardFilter::Dormant => self.status() == CardStatus::Dormant, CardFilter::New => self.status() == CardStatus::New, + CardFilter::Tag(tag) => self.tags.contains(tag), } } @@ -308,7 +313,7 @@ impl Widget for ServerCard { text: status.label().to_string(), font_size: 10.0, color: theme::launcher_chip_text_color(status.chip_tone()), - monospace: false, + ..Default::default() }); if self.layout_mode == CardLayoutMode::Card { @@ -326,7 +331,7 @@ impl Widget for ServerCard { text: profile_label, font_size: 10.0, color: theme::LT_TEXT_SECONDARY, - monospace: false, + ..Default::default() }); ctx.push_text_run(TextRun { @@ -335,7 +340,7 @@ impl Widget for ServerCard { text: self.display_name.clone(), font_size: 16.0, color: theme::LT_TEXT_PRIMARY, - monospace: false, + ..Default::default() }); ctx.push_text_run(TextRun { @@ -344,7 +349,7 @@ impl Widget for ServerCard { text: self.address.clone(), font_size: 11.0, color: theme::LT_TEXT_MUTED, - monospace: false, + ..Default::default() }); ctx.push_text_run(TextRun { @@ -353,7 +358,7 @@ impl Widget for ServerCard { text: self.relative_last_connected(), font_size: 11.0, color: theme::LT_TEXT_SECONDARY, - monospace: false, + ..Default::default() }); ctx.push_text_run(TextRun { @@ -362,7 +367,7 @@ impl Widget for ServerCard { text: self.last_info.clone(), font_size: 11.0, color: theme::LT_TEXT_MUTED, - monospace: false, + ..Default::default() }); } else { // Row text placement @@ -381,6 +386,7 @@ impl Widget for ServerCard { font_size: 14.0, color: accent, monospace: true, + ..Default::default() }); ctx.push_text_run(TextRun { x: r.x + 64.0, @@ -388,7 +394,7 @@ impl Widget for ServerCard { text: self.display_name.clone(), font_size: 14.0, color: theme::LT_TEXT_PRIMARY, - monospace: false, + ..Default::default() }); ctx.push_text_run(TextRun { x: r.x + 64.0, @@ -396,7 +402,7 @@ impl Widget for ServerCard { text: self.address.clone(), font_size: 11.0, color: theme::LT_TEXT_MUTED, - monospace: false, + ..Default::default() }); let status_end = status_rect.x + status_rect.w; @@ -406,7 +412,7 @@ impl Widget for ServerCard { text: self.relative_last_connected(), font_size: 12.0, color: theme::LT_TEXT_SECONDARY, - monospace: false, + ..Default::default() }); } diff --git a/crates/prism-client/src/ui/launcher/server_form.rs b/crates/prism-client/src/ui/launcher/server_form.rs index ec3fcf0..6dafdf7 100644 --- a/crates/prism-client/src/ui/launcher/server_form.rs +++ b/crates/prism-client/src/ui/launcher/server_form.rs @@ -177,7 +177,7 @@ impl Widget for ServerForm { text: title.to_string(), font_size: 16.0, color: theme::LT_TEXT_PRIMARY, - monospace: false, + ..Default::default() }); self.name_input.paint(ctx); diff --git a/crates/prism-client/src/ui/launcher/settings.rs b/crates/prism-client/src/ui/launcher/settings.rs index c1cf0a8..12bfa84 100644 --- a/crates/prism-client/src/ui/launcher/settings.rs +++ b/crates/prism-client/src/ui/launcher/settings.rs @@ -209,7 +209,7 @@ impl Widget for SettingsPanel { text: "Identity & Security".to_string(), font_size: theme::FONT_DISPLAY, color: theme::LT_TEXT_PRIMARY, - monospace: false, + ..Default::default() }); ctx.push_text_run(TextRun { @@ -218,7 +218,7 @@ impl Widget for SettingsPanel { text: "Manage your digital footprint and application settings.".to_string(), font_size: theme::FONT_BODY, color: theme::LT_TEXT_MUTED, - monospace: false, + ..Default::default() }); // Main Card Surface bounding all attributes @@ -236,7 +236,7 @@ impl Widget for SettingsPanel { text: title.to_string(), font_size: theme::FONT_BODY, color: theme::LT_TEXT_PRIMARY, - monospace: false, + ..Default::default() }); ctx.push_text_run(TextRun { x: content_x + 32.0, @@ -244,7 +244,7 @@ impl Widget for SettingsPanel { text: subtitle.to_string(), font_size: theme::FONT_CAPTION, color: theme::LT_TEXT_MUTED, - monospace: false, + ..Default::default() }); }; @@ -285,6 +285,7 @@ impl Widget for SettingsPanel { font_size: theme::FONT_LABEL, color: theme::PRIMARY_BLUE, monospace: true, + ..Default::default() }); cy += 74.0; @@ -309,7 +310,7 @@ impl Widget for SettingsPanel { text: "Trusted Device".to_string(), font_size: theme::FONT_CAPTION, color: theme::launcher_chip_text_color(theme::ChipTone::Success), - monospace: false, + ..Default::default() }); cy += 74.0; @@ -349,7 +350,7 @@ impl Widget for SettingsPanel { text: "Exclusive Keyboard Capture".to_string(), font_size: theme::FONT_LABEL, color: theme::LT_TEXT_PRIMARY, - monospace: false, + ..Default::default() }); self.exclusive_keyboard_toggle.paint(ctx); cy += 64.0; @@ -364,7 +365,7 @@ impl Widget for SettingsPanel { text: "Relative Mouse Movement".to_string(), font_size: theme::FONT_LABEL, color: theme::LT_TEXT_PRIMARY, - monospace: false, + ..Default::default() }); self.relative_mouse_toggle.paint(ctx); @@ -386,7 +387,7 @@ impl Widget for SettingsPanel { text: "REMOTE OUTPUT".to_string(), font_size: 10.0, color: audio_label_color, - monospace: false, + ..Default::default() }); self.audio_output_dropdown.paint(ctx); @@ -397,7 +398,7 @@ impl Widget for SettingsPanel { text: "LOCAL MIC PATH".to_string(), font_size: 10.0, color: audio_label_color, - monospace: false, + ..Default::default() }); self.mic_dropdown.paint(ctx); @@ -414,7 +415,7 @@ impl Widget for SettingsPanel { theme::LT_TEXT_PRIMARY[2], 0.3, ], - monospace: false, + ..Default::default() }); } diff --git a/crates/prism-client/src/ui/launcher/shell.rs b/crates/prism-client/src/ui/launcher/shell.rs index fc232e4..8cb8e71 100644 --- a/crates/prism-client/src/ui/launcher/shell.rs +++ b/crates/prism-client/src/ui/launcher/shell.rs @@ -234,7 +234,7 @@ impl LauncherShell { text: self.active_tab.title().to_string(), font_size: theme::FONT_DISPLAY, color: theme::LT_TEXT_PRIMARY, - monospace: false, + ..Default::default() }); ctx.push_text_run(TextRun { x: title_x, @@ -242,7 +242,7 @@ impl LauncherShell { text: self.active_tab.subtitle().to_string(), font_size: theme::FONT_BODY, color: theme::LT_TEXT_SECONDARY, - monospace: false, + ..Default::default() }); if self.ui_state == UiState::Connecting { @@ -264,7 +264,7 @@ impl LauncherShell { text: chip_text.to_string(), font_size: 12.0, color: theme::launcher_chip_text_color(theme::ChipTone::Accent), - monospace: false, + ..Default::default() }); } } @@ -280,7 +280,7 @@ impl LauncherShell { text: "Recent Connections".to_string(), font_size: 13.0, color: theme::LT_TEXT_SECONDARY, - monospace: false, + ..Default::default() }); ctx.push_glass_quad(theme::launcher_separator(Rect::new( self.content_rect.x, @@ -340,7 +340,7 @@ impl LauncherShell { text: "Delete connection".to_string(), font_size: theme::FONT_HEADLINE, color: theme::LT_TEXT_PRIMARY, - monospace: false, + ..Default::default() }); ctx.push_text_run(TextRun { x: panel.x + 20.0, @@ -348,7 +348,7 @@ impl LauncherShell { text: format!("Are you sure you want to remove \"{name}\"?"), font_size: theme::FONT_BODY, color: theme::LT_TEXT_SECONDARY, - monospace: false, + ..Default::default() }); let (cancel_rect, confirm_rect) = Self::delete_modal_buttons(panel); ctx.push_glass_quad(theme::glass_quad( @@ -372,7 +372,7 @@ impl LauncherShell { text: cancel_label.to_string(), font_size: 12.0, color: theme::LT_TEXT_SECONDARY, - monospace: false, + ..Default::default() }); ctx.push_text_run(TextRun { x: confirm_rect.x @@ -381,7 +381,7 @@ impl LauncherShell { text: delete_label.to_string(), font_size: 12.0, color: [1.0, 1.0, 1.0, 1.0], - monospace: false, + ..Default::default() }); } } diff --git a/crates/prism-client/src/ui/overlay/capsule.rs b/crates/prism-client/src/ui/overlay/capsule.rs index 87f9273..b408889 100644 --- a/crates/prism-client/src/ui/overlay/capsule.rs +++ b/crates/prism-client/src/ui/overlay/capsule.rs @@ -192,7 +192,7 @@ impl Widget for OverlayCapsule { text: "DECODE".into(), font_size: 10.0, color: txt_c, - monospace: false, + ..Default::default() }); bl_x += 46.0; ctx.push_text_run(TextRun { @@ -202,6 +202,7 @@ impl Widget for OverlayCapsule { font_size: 11.0, color: theme::ACCENT, monospace: true, + ..Default::default() }); bl_x += 50.0; @@ -211,7 +212,7 @@ impl Widget for OverlayCapsule { text: "RES".into(), font_size: 10.0, color: txt_c, - monospace: false, + ..Default::default() }); bl_x += 24.0; ctx.push_text_run(TextRun { @@ -224,6 +225,7 @@ impl Widget for OverlayCapsule { font_size: 11.0, color: theme::ACCENT, monospace: true, + ..Default::default() }); bl_x += 80.0; @@ -233,7 +235,7 @@ impl Widget for OverlayCapsule { text: "ACTIVE SESSION".into(), font_size: 10.0, color: theme::SUCCESS, - monospace: false, + ..Default::default() }); // Paint Disconnect Button (Bottom Corner) @@ -249,7 +251,7 @@ impl Widget for OverlayCapsule { text: "DISCONNECT".into(), font_size: 8.0, color: [1.0, 1.0, 1.0, 1.0], - monospace: false, + ..Default::default() }); } diff --git a/crates/prism-client/src/ui/overlay/conn_panel.rs b/crates/prism-client/src/ui/overlay/conn_panel.rs index 21c4eb1..104e922 100644 --- a/crates/prism-client/src/ui/overlay/conn_panel.rs +++ b/crates/prism-client/src/ui/overlay/conn_panel.rs @@ -129,7 +129,7 @@ impl Widget for ConnPanel { text: "Connection".into(), font_size: 13.0, color: theme::TEXT_PRIMARY, - monospace: false, + ..Default::default() }); self.server_label.paint(ctx); diff --git a/crates/prism-client/src/ui/overlay/display_panel.rs b/crates/prism-client/src/ui/overlay/display_panel.rs index 8ecec18..65ee59a 100644 --- a/crates/prism-client/src/ui/overlay/display_panel.rs +++ b/crates/prism-client/src/ui/overlay/display_panel.rs @@ -129,7 +129,7 @@ impl Widget for DisplayPanel { text: "Display".into(), font_size: 13.0, color: theme::TEXT_PRIMARY, - monospace: false, + ..Default::default() }); self.monitor_dropdown.paint(ctx); diff --git a/crates/prism-client/src/ui/overlay/perf_panel.rs b/crates/prism-client/src/ui/overlay/perf_panel.rs index 3a4d2eb..400b8f6 100644 --- a/crates/prism-client/src/ui/overlay/perf_panel.rs +++ b/crates/prism-client/src/ui/overlay/perf_panel.rs @@ -132,7 +132,7 @@ impl Widget for PerfPanel { text: "Performance".into(), font_size: 13.0, color: theme::TEXT_PRIMARY, - monospace: false, + ..Default::default() }); self.fps_label.paint(ctx); diff --git a/crates/prism-client/src/ui/overlay/quality_panel.rs b/crates/prism-client/src/ui/overlay/quality_panel.rs index 641f8ab..e516bef 100644 --- a/crates/prism-client/src/ui/overlay/quality_panel.rs +++ b/crates/prism-client/src/ui/overlay/quality_panel.rs @@ -112,7 +112,7 @@ impl Widget for QualityPanel { text: "Quality".into(), font_size: 13.0, color: theme::TEXT_PRIMARY, - monospace: false, + ..Default::default() }); self.profile_dropdown.paint(ctx); diff --git a/crates/prism-client/src/ui/overlay/stats_bar.rs b/crates/prism-client/src/ui/overlay/stats_bar.rs index c42d40b..ec9bbe8 100644 --- a/crates/prism-client/src/ui/overlay/stats_bar.rs +++ b/crates/prism-client/src/ui/overlay/stats_bar.rs @@ -159,7 +159,7 @@ impl Widget for StatsBar { text: "PRISM REMOTE".into(), font_size: 11.0, color: theme::ACCENT, - monospace: false, + ..Default::default() }); ctx.push_glass_quad(theme::separator(Rect::new( @@ -184,7 +184,7 @@ impl Widget for StatsBar { text: label.into(), font_size: 9.0, color: metric_label_color, - monospace: false, + ..Default::default() }); ctx.push_text_run(TextRun { x, @@ -193,6 +193,7 @@ impl Widget for StatsBar { font_size: 12.0, color: val_c, monospace: true, + ..Default::default() }); }; @@ -255,7 +256,7 @@ impl Widget for StatsBar { text: label.into(), font_size: 11.0, color: metric_label_color, - monospace: false, + ..Default::default() }); }; diff --git a/crates/prism-client/src/ui/widgets/button.rs b/crates/prism-client/src/ui/widgets/button.rs index 5fb6001..3625253 100644 --- a/crates/prism-client/src/ui/widgets/button.rs +++ b/crates/prism-client/src/ui/widgets/button.rs @@ -156,7 +156,7 @@ impl Widget for Button { text: self.label.clone(), font_size, color: text_color, - monospace: false, + ..Default::default() }); } diff --git a/crates/prism-client/src/ui/widgets/checkbox.rs b/crates/prism-client/src/ui/widgets/checkbox.rs index 5d78bbd..c20dff5 100644 --- a/crates/prism-client/src/ui/widgets/checkbox.rs +++ b/crates/prism-client/src/ui/widgets/checkbox.rs @@ -81,7 +81,7 @@ impl Widget for Checkbox { text: "x".into(), font_size: 12.0, color: theme::accent(0.60 + self.fill_anim.value() * 0.30), - monospace: false, + ..Default::default() }); } @@ -92,7 +92,7 @@ impl Widget for Checkbox { text: self.label.clone(), font_size: 13.0, color: theme::TEXT_SECONDARY, - monospace: false, + ..Default::default() }); } diff --git a/crates/prism-client/src/ui/widgets/dropdown.rs b/crates/prism-client/src/ui/widgets/dropdown.rs index 697bb28..768fc70 100644 --- a/crates/prism-client/src/ui/widgets/dropdown.rs +++ b/crates/prism-client/src/ui/widgets/dropdown.rs @@ -107,7 +107,7 @@ impl Widget for Dropdown { text: label, font_size: 13.0, color: header_text_color, - monospace: false, + ..Default::default() }); // Open dropdown items @@ -174,7 +174,7 @@ impl Widget for Dropdown { text: option.clone(), font_size: 13.0, color: item_text_color, - monospace: false, + ..Default::default() }); } } diff --git a/crates/prism-client/src/ui/widgets/label.rs b/crates/prism-client/src/ui/widgets/label.rs index 04e9f80..4c3bed1 100644 --- a/crates/prism-client/src/ui/widgets/label.rs +++ b/crates/prism-client/src/ui/widgets/label.rs @@ -59,6 +59,7 @@ impl Widget for Label { font_size: self.font_size, color: self.color, monospace: self.monospace, + ..Default::default() }); } diff --git a/crates/prism-client/src/ui/widgets/mod.rs b/crates/prism-client/src/ui/widgets/mod.rs index d372ed2..1bff153 100644 --- a/crates/prism-client/src/ui/widgets/mod.rs +++ b/crates/prism-client/src/ui/widgets/mod.rs @@ -42,6 +42,13 @@ pub struct Rect { } impl Rect { + pub const ZERO: Rect = Rect { + x: 0.0, + y: 0.0, + w: 0.0, + h: 0.0, + }; + pub fn new(x: f32, y: f32, w: f32, h: f32) -> Self { Self { x, y, w, h } } @@ -80,6 +87,19 @@ pub struct GlassQuad { pub noise_intensity: f32, } +impl Default for GlassQuad { + fn default() -> Self { + Self { + rect: Rect::ZERO, + blur_rect: Rect::ZERO, + tint: [0.0; 4], + border_color: [0.0; 4], + corner_radius: 0.0, + noise_intensity: 0.0, + } + } +} + /// Text draw command. #[derive(Debug, Clone)] pub struct TextRun { @@ -89,6 +109,23 @@ pub struct TextRun { pub font_size: f32, pub color: [f32; 4], pub monospace: bool, + pub bold: bool, + pub icon: bool, +} + +impl Default for TextRun { + fn default() -> Self { + Self { + x: 0.0, + y: 0.0, + text: String::new(), + font_size: 14.0, + color: [0.0, 0.0, 0.0, 1.0], + monospace: false, + bold: false, + icon: false, + } + } } /// Accent glow draw command. @@ -350,7 +387,7 @@ mod tests { text: "hello".into(), font_size: 14.0, color: [1.0; 4], - monospace: false, + ..Default::default() }); assert_eq!(ctx.text_runs.len(), 1); } diff --git a/crates/prism-client/src/ui/widgets/monitor_map.rs b/crates/prism-client/src/ui/widgets/monitor_map.rs index b42c58c..426f70b 100644 --- a/crates/prism-client/src/ui/widgets/monitor_map.rs +++ b/crates/prism-client/src/ui/widgets/monitor_map.rs @@ -127,7 +127,7 @@ impl Widget for MonitorMap { } else { theme::TEXT_SECONDARY }, - monospace: false, + ..Default::default() }); } } diff --git a/crates/prism-client/src/ui/widgets/segmented.rs b/crates/prism-client/src/ui/widgets/segmented.rs index b52ac7d..ed3a96e 100644 --- a/crates/prism-client/src/ui/widgets/segmented.rs +++ b/crates/prism-client/src/ui/widgets/segmented.rs @@ -130,7 +130,7 @@ impl Widget for SegmentedControl { text: label.clone(), font_size: 12.0, color: text_color, - monospace: false, + ..Default::default() }); } } diff --git a/crates/prism-client/src/ui/widgets/slider.rs b/crates/prism-client/src/ui/widgets/slider.rs index ad8af74..aa8a911 100644 --- a/crates/prism-client/src/ui/widgets/slider.rs +++ b/crates/prism-client/src/ui/widgets/slider.rs @@ -122,7 +122,7 @@ impl Widget for Slider { text: self.label.clone(), font_size: 12.0, color: label_color, - monospace: false, + ..Default::default() }); ctx.push_text_run(TextRun { x: self.rect.x + self.rect.w - theme::text_width(&value_text, 12.0), @@ -131,6 +131,7 @@ impl Widget for Slider { font_size: 12.0, color: value_color, monospace: true, + ..Default::default() }); // Track background diff --git a/crates/prism-client/src/ui/widgets/text_input.rs b/crates/prism-client/src/ui/widgets/text_input.rs index 9ee8482..ea6dcf0 100644 --- a/crates/prism-client/src/ui/widgets/text_input.rs +++ b/crates/prism-client/src/ui/widgets/text_input.rs @@ -98,7 +98,7 @@ impl Widget for TextInput { text: display_text, font_size: 14.0, color, - monospace: false, + ..Default::default() }); } diff --git a/docs/superpowers/plans/2026-04-03-launcher-ui-polish.md b/docs/superpowers/plans/2026-04-03-launcher-ui-polish.md index c104d2a..2682a95 100644 --- a/docs/superpowers/plans/2026-04-03-launcher-ui-polish.md +++ b/docs/superpowers/plans/2026-04-03-launcher-ui-polish.md @@ -45,7 +45,7 @@ Check off each phase/pass as it is completed. Refer to **Section 10 — Recommen ### Passes -- [ ] **Pass 0 — Data Prerequisites** (Phase 0: TASK-P01 → TASK-P06) +- [x] **Pass 0 — Data Prerequisites** (Phase 0: TASK-P01 → TASK-P06) - [ ] **Pass 1 — Foundations** (Phases 1–3: TASK-002 → TASK-015) - [ ] **Pass 2 — Icons & Header** (Phases 5–6: TASK-024 → TASK-033) - [ ] **Pass 3 — Data & Screens** (Phases 4, 7–9: TASK-016 → TASK-023, TASK-034 → TASK-064) @@ -54,7 +54,7 @@ Check off each phase/pass as it is completed. Refer to **Section 10 — Recommen ### Phases -- [ ] Phase 0 — Data-Layer Prerequisites (6 tasks: P01–P06) +- [x] Phase 0 — Data-Layer Prerequisites (6 tasks: P01–P06) - [ ] Phase 1 — Bold Text Support (3 tasks: 002–004) - [ ] Phase 2 — Primary Button Color Fix (3 tasks: 005–007) - [ ] Phase 3 — Sidebar Geometry Overhaul (7 tasks: 008–015) @@ -405,13 +405,13 @@ mode is handled exclusively by the overlay and is NOT modified by this plan. | Task | Description | Completed | Date | |------|-------------|-----------|------| -| TASK-P01 | **`ServerStatus` enum:** In `crates/prism-client/src/config/servers.rs`, add `pub enum ServerStatus { Online, Sleeping, Unreachable }`. The design uses status to drive chip color (Success/Warning/Danger), contextual button labels, and unreachable-card dimming. | | | -| TASK-P02 | **Heuristic status derivation:** Add `pub fn derived_status(&self) -> ServerStatus` to `SavedServer`. Logic: if `last_connected` is `None` → `Unreachable`; if age < 6 hours → `Online`; if age < 7 days → `Sleeping`; else → `Unreachable`. This is a temporary heuristic until real-time discovery/ping is implemented. Document the intent to replace with a runtime probe in a follow-up plan. | | | -| TASK-P03 | **Schema extensions:** Add optional fields to `SavedServer`: `pub os_label: Option` (e.g. "WINDOWS 11 PRO"), `pub tags: Vec` (e.g. ["WORK"]), `pub wol_supported: bool` (default `false`), `pub last_latency_ms: Option`. These fields are `Option`/defaulted so existing serialized data remains compatible. Update `serde` derives and `ServerStore` snapshot logic. | | | -| TASK-P04 | **Filter alignment:** In `ui/launcher/server_card.rs`, update `CardFilter` to support tag-based filters in addition to time-based ones. Add variants: `Tag(String)`. Update `label()` to return the tag name. The filter bar (Phase 7) can render both built-in and tag-derived pills. The current `All`/`Recent`/`Dormant`/`New` filters remain; tag pills are appended dynamically from the union of all servers' `tags`. | | | -| TASK-P05 | **`TextRun` extensibility (merged from Phase 1 TASK-001):** In `ui/widgets/mod.rs`, add `bold: bool` and `icon: bool` fields to `TextRun` (both default `false`). Implement `Default` manually (not `#[derive(Default)]` — `font_size` must default to `14.0`, not `0.0`). Migrate ALL existing `TextRun { ... }` construction sites to use `..Default::default()` syntax. This makes `TextRun` forward-compatible — Phase 1 (bold rendering) and Phase 5 (icon rendering) consume these fields without re-editing the struct or its `Default` impl. | | | -| TASK-P05a | **`GlassQuad` `Default` impl:** In `ui/widgets/mod.rs`, implement `Default` for `GlassQuad` with zeroed fields: `rect: Rect::ZERO`, `blur_rect: Rect::ZERO`, `tint: [0.0; 4]`, `border_color: [0.0; 4]`, `corner_radius: 0.0`, `noise_intensity: 0.0`. This enables `GlassQuad { rect, tint, corner_radius, ..Default::default() }` shorthand throughout all phases. | | | -| TASK-P06 | `cargo check -p prism-client` — verify compilation. All existing `TextRun` and `GlassQuad` construction sites compile with the new `Default` impls. | | | +| TASK-P01 | **`ServerStatus` enum:** In `crates/prism-client/src/config/servers.rs`, add `pub enum ServerStatus { Online, Sleeping, Unreachable }`. The design uses status to drive chip color (Success/Warning/Danger), contextual button labels, and unreachable-card dimming. | ✅ | 2026-04-03 | +| TASK-P02 | **Heuristic status derivation:** Add `pub fn derived_status(&self) -> ServerStatus` to `SavedServer`. Logic: if `last_connected` is `None` → `Unreachable`; if age < 6 hours → `Online`; if age < 7 days → `Sleeping`; else → `Unreachable`. This is a temporary heuristic until real-time discovery/ping is implemented. Document the intent to replace with a runtime probe in a follow-up plan. | ✅ | 2026-04-03 | +| TASK-P03 | **Schema extensions:** Add optional fields to `SavedServer`: `pub os_label: Option` (e.g. "WINDOWS 11 PRO"), `pub tags: Vec` (e.g. ["WORK"]), `pub wol_supported: bool` (default `false`), `pub last_latency_ms: Option`. These fields are `Option`/defaulted so existing serialized data remains compatible. Update `serde` derives and `ServerStore` snapshot logic. | ✅ | 2026-04-03 | +| TASK-P04 | **Filter alignment:** In `ui/launcher/server_card.rs`, update `CardFilter` to support tag-based filters in addition to time-based ones. Add variants: `Tag(String)`. Update `label()` to return the tag name. The filter bar (Phase 7) can render both built-in and tag-derived pills. The current `All`/`Recent`/`Dormant`/`New` filters remain; tag pills are appended dynamically from the union of all servers' `tags`. | ✅ | 2026-04-03 | +| TASK-P05 | **`TextRun` extensibility (merged from Phase 1 TASK-001):** In `ui/widgets/mod.rs`, add `bold: bool` and `icon: bool` fields to `TextRun` (both default `false`). Implement `Default` manually (not `#[derive(Default)]` — `font_size` must default to `14.0`, not `0.0`). Migrate ALL existing `TextRun { ... }` construction sites to use `..Default::default()` syntax. This makes `TextRun` forward-compatible — Phase 1 (bold rendering) and Phase 5 (icon rendering) consume these fields without re-editing the struct or its `Default` impl. | ✅ | 2026-04-03 | +| TASK-P05a | **`GlassQuad` `Default` impl:** In `ui/widgets/mod.rs`, implement `Default` for `GlassQuad` with zeroed fields: `rect: Rect::ZERO`, `blur_rect: Rect::ZERO`, `tint: [0.0; 4]`, `border_color: [0.0; 4]`, `corner_radius: 0.0`, `noise_intensity: 0.0`. This enables `GlassQuad { rect, tint, corner_radius, ..Default::default() }` shorthand throughout all phases. | ✅ | 2026-04-03 | +| TASK-P06 | `cargo check -p prism-client` — verify compilation. All existing `TextRun` and `GlassQuad` construction sites compile with the new `Default` impls. | ✅ | 2026-04-03 | #### Phase 0 — Implementation Detail