annatomic 0.1.0

The Annatomic annotation editor is intended to be used for the [RIDGES corpus](https://www.linguistik.hu-berlin.de/en/institut-en/professuren-en/korpuslinguistik/research/ridges-projekt). It is based on [graphANNIS](https://github.com/korpling/graphANNIS) and thus is internal data model is in principle suitable for a wide range of annotation concepts. "
Documentation
use std::collections::BTreeSet;

use egui::{
    Button, Context, Frame, Id, InnerResponse, Layout, RichText, ScrollArea, UiBuilder, Widget,
};
use graphannis::graph::AnnoKey;
use serde::{Deserialize, Serialize};

pub struct FilterWidget {
    available_node_annos: BTreeSet<AnnoKey>,
    widget_id: Id,
}

#[derive(Clone, Default, Debug, Serialize, Deserialize)]
pub struct FilterWidgetState {
    pub selected_node_annos: BTreeSet<AnnoKey>,
}

#[derive(Default, Debug)]
pub struct FilterWidgetOutput {
    pub selected_node_annos: BTreeSet<AnnoKey>,
}

impl FilterWidget {
    pub fn new<I: Into<Id>>(available_node_annos: BTreeSet<AnnoKey>, widget_id: I) -> Self {
        Self {
            available_node_annos,
            widget_id: widget_id.into(),
        }
    }

    pub fn load_state(&self, ctx: &Context) -> FilterWidgetState {
        ctx.data_mut(|d| d.get_persisted(self.widget_id))
            .unwrap_or_default()
    }

    pub fn store_state(&self, ctx: &Context, state: FilterWidgetState) {
        ctx.data_mut(|d| d.insert_persisted(self.widget_id, state));
    }

    pub fn show(self, ui: &mut egui::Ui) -> InnerResponse<FilterWidgetOutput> {
        let mut state = self.load_state(ui.ctx());
        let style = ui.style().clone();

        let response = Frame::group(&style)
            .show(ui, |ui| {
                ui.vertical(|ui| {
                    let header_builder =
                        UiBuilder::new().layout(Layout::left_to_right(egui::Align::Min));
                    ui.scope_builder(header_builder, |ui| {
                        ui.label(RichText::new("Show spans").underline());
                        // Add buttons to select all or none annotation keys.
                        // Highlight them if all or none are currently, selected.
                        let mut all_button = Button::new("All");
                        if !self.available_node_annos.is_empty()
                            && state
                                .selected_node_annos
                                .is_superset(&self.available_node_annos)
                        {
                            all_button = all_button.fill(style.visuals.selection.bg_fill);
                        }
                        if all_button.ui(ui).clicked() {
                            state
                                .selected_node_annos
                                .extend(self.available_node_annos.clone());
                        }
                        let mut none_button = Button::new("None");
                        if !self.available_node_annos.is_empty()
                            && state
                                .selected_node_annos
                                .is_disjoint(&self.available_node_annos)
                        {
                            none_button = none_button.fill(style.visuals.selection.bg_fill);
                        }
                        if none_button.ui(ui).clicked() {
                            state.selected_node_annos.clear();
                        }
                    });
                    ScrollArea::vertical().show(ui, |ui| {
                        for key in &self.available_node_annos {
                            let is_visible = state.selected_node_annos.contains(key);
                            let displayed_icon = if is_visible {
                                egui_phosphor::regular::EYE
                            } else {
                                egui_phosphor::regular::EYE_SLASH
                            };
                            let button_color = if is_visible {
                                style.visuals.selection.bg_fill
                            } else {
                                style.visuals.widgets.active.bg_fill
                            };
                            let button = if key.ns.is_empty() {
                                Button::new((key.name.as_str(), displayed_icon)).fill(button_color)
                            } else {
                                Button::new((
                                    RichText::new(key.ns.as_str()).small(),
                                    key.name.as_str(),
                                    displayed_icon,
                                ))
                                .fill(button_color)
                            };
                            if button.ui(ui).clicked() {
                                if is_visible {
                                    state.selected_node_annos.remove(key);
                                } else {
                                    state.selected_node_annos.insert(key.clone());
                                }
                            }
                        }
                    });
                })
            })
            .response;
        let output = FilterWidgetOutput {
            selected_node_annos: state.selected_node_annos.clone(),
        };
        self.store_state(ui.ctx(), state);
        InnerResponse::new(output, response)
    }
    pub fn select_anno_key(&mut self, anno_key: AnnoKey, ui: &egui::Ui) {
        let mut state = self.load_state(ui.ctx());
        state.selected_node_annos.insert(anno_key);
        self.store_state(ui.ctx(), state);
    }
}

impl Widget for FilterWidget {
    fn ui(self, ui: &mut egui::Ui) -> egui::Response {
        self.show(ui).response
    }
}