nmrs_gui/ui/
wired_devices.rs

1use gtk::prelude::*;
2use gtk::Align;
3use gtk::GestureClick;
4use gtk::{Box, Image, Label, ListBox, ListBoxRow, Orientation};
5use nmrs::{models, NetworkManager};
6use std::rc::Rc;
7
8use crate::ui::wired_page::WiredPage;
9
10pub struct WiredDeviceRowController {
11    pub row: gtk::ListBoxRow,
12    pub arrow: gtk::Image,
13    pub ctx: Rc<WiredDevicesContext>,
14    pub device: models::Device,
15    pub details_page: Rc<WiredPage>,
16}
17
18pub struct WiredDevicesContext {
19    pub nm: Rc<NetworkManager>,
20    pub on_success: Rc<dyn Fn()>,
21    pub status: Label,
22    pub stack: gtk::Stack,
23    pub parent_window: gtk::ApplicationWindow,
24}
25
26impl WiredDeviceRowController {
27    pub fn new(
28        row: gtk::ListBoxRow,
29        arrow: gtk::Image,
30        ctx: Rc<WiredDevicesContext>,
31        device: models::Device,
32        details_page: Rc<WiredPage>,
33    ) -> Self {
34        Self {
35            row,
36            arrow,
37            ctx,
38            device,
39            details_page,
40        }
41    }
42
43    pub fn attach(&self) {
44        self.attach_arrow();
45        self.attach_row_double();
46    }
47
48    fn attach_arrow(&self) {
49        let click = GestureClick::new();
50
51        let device = self.device.clone();
52        let stack = self.ctx.stack.clone();
53        let page = self.details_page.clone();
54
55        click.connect_pressed(move |_, _, _, _| {
56            let device_c = device.clone();
57            let stack_c = stack.clone();
58            let page_c = page.clone();
59
60            glib::MainContext::default().spawn_local(async move {
61                page_c.update(&device_c);
62                stack_c.set_visible_child_name("wired-details");
63            });
64        });
65
66        self.arrow.add_controller(click);
67    }
68
69    fn attach_row_double(&self) {
70        let click = GestureClick::new();
71
72        let ctx = self.ctx.clone();
73        let device = self.device.clone();
74        let interface = device.interface.clone();
75
76        let status = ctx.status.clone();
77        let window = ctx.parent_window.clone();
78        let on_success = ctx.on_success.clone();
79
80        click.connect_pressed(move |_, n, _, _| {
81            if n != 2 {
82                return;
83            }
84
85            status.set_text(&format!("Connecting to {interface}..."));
86
87            let nm_c = ctx.nm.clone();
88            let status_c = status.clone();
89            let window_c = window.clone();
90            let on_success_c = on_success.clone();
91
92            glib::MainContext::default().spawn_local(async move {
93                window_c.set_sensitive(false);
94                match nm_c.connect_wired().await {
95                    Ok(_) => {
96                        status_c.set_text("");
97                        on_success_c();
98                    }
99                    Err(e) => status_c.set_text(&format!("Failed to connect: {e}")),
100                }
101                window_c.set_sensitive(true);
102                status_c.set_text("");
103            });
104        });
105
106        self.row.add_controller(click);
107    }
108}
109
110pub fn wired_devices_view(
111    ctx: Rc<WiredDevicesContext>,
112    devices: &[models::Device],
113    details_page: Rc<WiredPage>,
114) -> ListBox {
115    let list = ListBox::new();
116
117    for device in devices {
118        let row = ListBoxRow::new();
119        let hbox = Box::new(Orientation::Horizontal, 6);
120
121        row.add_css_class("network-selection");
122
123        if device.state == models::DeviceState::Activated {
124            row.add_css_class("connected");
125        }
126
127        let display_name = format!("{} ({})", device.interface, device.device_type);
128        hbox.append(&Label::new(Some(&display_name)));
129
130        if device.state == models::DeviceState::Activated {
131            let connected_label = Label::new(Some("Connected"));
132            connected_label.add_css_class("connected-label");
133            hbox.append(&connected_label);
134        }
135
136        let spacer = Box::new(Orientation::Horizontal, 0);
137        spacer.set_hexpand(true);
138        hbox.append(&spacer);
139
140        // Only show state for meaningful states (not transitional ones)
141        let state_text = match device.state {
142            models::DeviceState::Activated => Some("Connected"),
143            models::DeviceState::Disconnected => Some("Disconnected"),
144            models::DeviceState::Unavailable => Some("Unavailable"),
145            models::DeviceState::Failed => Some("Failed"),
146            // Hide transitional states (Unmanaged, Prepare, Config, etc)
147            _ => None,
148        };
149
150        if let Some(text) = state_text {
151            let state_label = Label::new(Some(text));
152            state_label.add_css_class(match device.state {
153                models::DeviceState::Activated => "network-good",
154                models::DeviceState::Unavailable
155                | models::DeviceState::Disconnected
156                | models::DeviceState::Failed => "network-poor",
157                _ => "network-okay",
158            });
159            hbox.append(&state_label);
160        }
161
162        let icon = Image::from_icon_name("network-wired-symbolic");
163        icon.add_css_class("wired-icon");
164        hbox.append(&icon);
165
166        let arrow = Image::from_icon_name("go-next-symbolic");
167        arrow.set_halign(Align::End);
168        arrow.add_css_class("network-arrow");
169        arrow.set_cursor_from_name(Some("pointer"));
170        hbox.append(&arrow);
171
172        row.set_child(Some(&hbox));
173
174        let controller = WiredDeviceRowController::new(
175            row.clone(),
176            arrow.clone(),
177            ctx.clone(),
178            device.clone(),
179            details_page.clone(),
180        );
181
182        controller.attach();
183
184        list.append(&row);
185    }
186    list
187}