ua-client 0.1.0

Native OPC UA browser/inspector GUI built on async-opcua and egui
use crate::messages::UiAction;
use crate::model::{AppModel, DetailTab};
use crate::types::ReferenceRow;

pub fn draw(model: &AppModel, ui: &mut egui::Ui, actions: &mut Vec<UiAction>) {
    ui.horizontal(|ui| {
        tab_button(ui, model, actions, DetailTab::Attributes, "Attributes");
        tab_button(ui, model, actions, DetailTab::Events, "Events");
        tab_button(ui, model, actions, DetailTab::DataChanges, "Data Changes");
        tab_button(ui, model, actions, DetailTab::References, "References");
        ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| {
            if ui.button("Refresh").clicked() {
                actions.push(UiAction::RefreshClicked);
            }
        });
    });
    ui.separator();

    match model.active_tab {
        DetailTab::References => draw_references(model, ui),
        DetailTab::Attributes => draw_todo(ui, "Attributes tab — coming next."),
        DetailTab::Events => draw_todo(ui, "Events tab — coming next."),
        DetailTab::DataChanges => draw_todo(ui, "Data Changes tab — coming next."),
    }
}

fn tab_button(
    ui: &mut egui::Ui,
    model: &AppModel,
    actions: &mut Vec<UiAction>,
    tab: DetailTab,
    label: &str,
) {
    let selected = model.active_tab == tab;
    if ui.selectable_label(selected, label).clicked() && !selected {
        actions.push(UiAction::TabSelected(tab));
    }
}

fn draw_todo(ui: &mut egui::Ui, msg: &str) {
    ui.label(egui::RichText::new(msg).italics().weak());
}

fn draw_references(model: &AppModel, ui: &mut egui::Ui) {
    if model.selected.is_none() {
        ui.label(egui::RichText::new("Select a node in the tree").italics().weak());
        return;
    }
    if model.references_loading {
        ui.horizontal(|ui| {
            ui.spinner();
            ui.label("Loading references…");
        });
        return;
    }
    let Some(refs) = model.references.as_ref() else {
        ui.label(egui::RichText::new("No data").italics().weak());
        return;
    };
    if refs.is_empty() {
        ui.label("(no references)");
        return;
    }

    egui::ScrollArea::both().show(ui, |ui| {
        use egui_extras::{Column, TableBuilder};
        TableBuilder::new(ui)
            .striped(true)
            .resizable(true)
            .column(Column::auto().at_least(30.0))
            .column(Column::auto().at_least(120.0))
            .column(Column::auto().at_least(120.0))
            .column(Column::auto().at_least(120.0))
            .column(Column::auto().at_least(80.0))
            .column(Column::remainder().at_least(140.0))
            .header(20.0, |mut header| {
                header.col(|ui| { ui.strong("Dir"); });
                header.col(|ui| { ui.strong("ReferenceType"); });
                header.col(|ui| { ui.strong("Target"); });
                header.col(|ui| { ui.strong("BrowseName"); });
                header.col(|ui| { ui.strong("NodeClass"); });
                header.col(|ui| { ui.strong("NodeId"); });
            })
            .body(|mut body| {
                for r in refs {
                    body.row(18.0, |mut row| {
                        row.col(|ui| { ui.label(if r.is_forward { "" } else { "" }); });
                        row.col(|ui| { ui.label(&r.reference_type); });
                        row.col(|ui| { ui.label(target_label(r)); });
                        row.col(|ui| { ui.label(&r.target_browse_name); });
                        row.col(|ui| { ui.label(format!("{:?}", r.target_node_class)); });
                        row.col(|ui| { ui.label(r.target_node_id.to_string()); });
                    });
                }
            });
    });
}

fn target_label(r: &ReferenceRow) -> &str {
    if r.target_display_name.is_empty() {
        &r.target_browse_name
    } else {
        &r.target_display_name
    }
}