use egui::{
Color32, Context, CornerRadius, Frame, Id, InnerResponse, Key, Label, Layout, Modifiers,
RichText, Sense, Stroke, TextEdit, UiBuilder, Vec2, Widget, WidgetInfo, WidgetWithState,
};
use graphannis::graph::{AnnoKey, Annotation};
use graphannis_core::{graph::ANNIS_NS, util::join_qname};
use itertools::Itertools;
use serde::{Deserialize, Serialize};
use crate::app::{data::Span, widgets::create_multiple_anno_key_labels};
use super::token::SelectionType;
#[derive(Debug)]
pub struct SpanWidget<'a> {
span: &'a Span,
selected: SelectionType,
}
#[derive(Default, Debug)]
pub struct SpanWidgetOutput {
pub selected: bool,
pub changed_annotations: Vec<Annotation>,
}
#[derive(Clone, Default, Debug, Serialize, Deserialize)]
pub struct SpanWidgetState {
pub edited_value: Option<(AnnoKey, String)>,
}
impl<'a> Widget for SpanWidget<'a> {
fn ui(self, ui: &mut egui::Ui) -> egui::Response {
self.show(ui).response
}
}
impl<'a> WidgetWithState for SpanWidget<'a> {
type State = SpanWidgetState;
}
impl<'s> SpanWidget<'s> {
pub fn new(span: &'s Span, selected: SelectionType) -> Self {
SpanWidget { span, selected }
}
fn create_widget_id(&self) -> Id {
Id::from(self.span.node_name.clone())
}
pub fn show(self, ui: &mut egui::Ui) -> InnerResponse<SpanWidgetOutput> {
let widget_id = self.create_widget_id();
let mut state = SpanWidgetState::load(ui.ctx(), widget_id).unwrap_or_default();
let mut output = SpanWidgetOutput::default();
let (text_color, bg_color) = if self.selected == SelectionType::None {
(Color32::WHITE, Color32::DARK_GRAY)
} else {
(Color32::BLACK, ui.style().visuals.selection.bg_fill)
};
let widget_width = ui.max_rect().width();
let frame_widget = Frame::group(ui.style())
.stroke(Stroke::NONE)
.inner_margin(3)
.fill(bg_color)
.corner_radius(CornerRadius::ZERO);
let frame_margin = frame_widget.total_margin();
let mut frame_response = frame_widget
.show(ui, |ui| {
let min_label_height = 20.0;
let desired_size = Vec2::new(
widget_width - frame_margin.left - frame_margin.right,
min_label_height,
);
ui.vertical(|ui| {
for (current_key, current_value) in &self.span.labels {
if current_key.ns != ANNIS_NS {
if let Some((key, value)) = &mut state.edited_value {
let value_edit = TextEdit::singleline(value)
.clip_text(true)
.desired_width(widget_width)
.show(ui);
if value_edit.response.lost_focus() {
if ui.input_mut(|i| i.consume_key(Modifiers::NONE, Key::Enter))
{
output.changed_annotations.push(Annotation {
key: key.clone(),
val: value.as_str().into(),
});
}
state.edited_value = None;
} else if !value_edit.response.has_focus() {
value_edit.response.request_focus();
if !ui.clip_rect().contains_rect(value_edit.response.rect) {
value_edit.response.scroll_to_me(Some(egui::Align::Min));
}
}
} else {
let (mut space_for_label, _) =
ui.allocate_exact_size(desired_size, Sense::empty());
let visible_range = ui.clip_rect().x_range();
let offset = visible_range.min - space_for_label.left();
if offset > 0.0 && (offset + 15.0) < space_for_label.width() {
space_for_label.set_left(space_for_label.left() + offset);
}
let lbl =
Label::new(RichText::new(current_value).color(text_color))
.halign(egui::Align::Min)
.wrap_mode(egui::TextWrapMode::Truncate);
let label_response = ui
.scope_builder(
UiBuilder::new()
.max_rect(space_for_label)
.layout(Layout::left_to_right(egui::Align::Center)),
|ui| ui.add(lbl),
)
.inner;
if label_response.hovered() {
ui.ctx().set_cursor_icon(egui::CursorIcon::PointingHand);
}
if self.selected == SelectionType::None {
if label_response.clicked() {
output.selected = true;
}
} else if label_response.clicked()
|| ui
.ctx()
.input_mut(|i| i.consume_key(Modifiers::NONE, Key::Enter))
{
state.edited_value =
Some((current_key.clone(), current_value.clone()));
}
}
}
}
});
})
.response;
if self.selected == SelectionType::None {
frame_response = frame_response.interact(Sense::click());
}
frame_response = frame_response.on_hover_ui(|ui| {
let keys = self.span.labels.keys().cloned().collect_vec();
create_multiple_anno_key_labels(&keys, &self.span.node_name, ui);
});
if frame_response.hovered() {
ui.ctx().set_cursor_icon(egui::CursorIcon::PointingHand);
}
if self.selected == SelectionType::None && frame_response.clicked() {
output.selected = true;
}
SpanWidgetState::store(state, ui.ctx(), widget_id);
frame_response.widget_info(move || {
let annos = self
.span
.labels
.iter()
.map(|(key, value)| format!("{}={}", join_qname(&key.ns, &key.name), value))
.join(", ");
let widget_label = format!("Span annotation {annos} ({})", self.span.node_name);
WidgetInfo::labeled(egui::WidgetType::Other, true, widget_label)
});
InnerResponse {
inner: output,
response: frame_response,
}
}
pub fn request_edit_label_value(&mut self, ui: &egui::Ui) {
let widget_id = self.create_widget_id();
let mut state = SpanWidgetState::load(ui.ctx(), widget_id).unwrap_or_default();
if state.edited_value.is_none()
&& let Some((key, value)) = self.span.labels.first_key_value()
{
state.edited_value = Some((key.clone(), value.clone()));
};
SpanWidgetState::store(state, ui.ctx(), widget_id);
}
}
impl SpanWidgetState {
pub fn load(ctx: &Context, id: Id) -> Option<Self> {
ctx.data_mut(|d| d.get_persisted(id))
}
pub fn store(self, ctx: &Context, id: Id) {
ctx.data_mut(|d| d.insert_persisted(id, self));
}
}