use crate::common::ToOpenTimelineType;
use crate::consts::REMOVE_BUTTON_WIDTH;
use bool_tag_expr::{Tag, TagError, TagName, TagValue};
use eframe::egui::{Context, TextEdit, Ui};
use open_timeline_crud::CrudError;
use open_timeline_gui_core::{
Draw, ErrorStyle, ShowRemoveButton, Valid, ValidAsynchronous, ValidSynchronous,
ValiditySynchronous, ValitityStatus, body_text_height, keyboard_input_cmd_and_enter,
keyboard_input_cmd_and_k, widget_x_spacing,
};
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum TagFocusRequestTarget {
Name,
Value,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum RequestedAction {
Remove,
AddNew(TagFocusRequestTarget),
}
#[derive(Debug)]
pub struct TagGui {
pub name: String,
pub value: String,
pub requested_action: Option<RequestedAction>,
component_to_focus_on: Option<TagFocusRequestTarget>,
show_remove_button: ShowRemoveButton,
validity: ValitityStatus<(), CrudError>,
}
impl TagGui {
pub fn new(
show_remove_button: ShowRemoveButton,
to_focus_on: Option<TagFocusRequestTarget>,
) -> Self {
let mut new = Self {
name: String::new(),
value: String::new(),
requested_action: None,
component_to_focus_on: to_focus_on,
show_remove_button,
validity: ValitityStatus::from(ValiditySynchronous::Valid, None),
};
new.update_validity();
new
}
pub fn from_tag(tag: Tag, show_remove_button: ShowRemoveButton) -> Self {
let name = match &tag.name {
Some(tag) => tag.to_string(),
None => String::new(),
};
let value = tag.value.to_string();
Self {
name,
value,
requested_action: None,
component_to_focus_on: None,
show_remove_button,
validity: ValitityStatus::from(ValiditySynchronous::Valid, None),
}
}
pub fn to_be_removed(&self) -> bool {
matches!(self.requested_action, Some(RequestedAction::Remove))
}
pub fn invalid_msg(&self) -> String {
self.validity.synchronous().invalid_msg()
}
}
impl ValidSynchronous for TagGui {
fn is_valid_synchronous(&self) -> bool {
self.validity.synchronous() == ValiditySynchronous::Valid
}
fn update_validity_synchronous(&mut self) {
if !self.name.trim().is_empty() {
match TagName::from(&self.name) {
Ok(tag_name) => Some(tag_name),
Err(error) => {
self.validity
.set_synchronous(ValiditySynchronous::Invalid(error.to_string()));
return;
}
};
};
if let Err(error) = TagValue::from(&self.value) {
let error_msg = match error {
TagError::Empty => String::from("Tag name cannot be empty"),
_ => error.to_string(),
};
self.validity
.set_synchronous(ValiditySynchronous::Invalid(error_msg));
return;
};
self.validity.set_synchronous(ValiditySynchronous::Valid);
}
fn validity_synchronous(&self) -> ValiditySynchronous {
self.validity.synchronous()
}
}
impl ValidAsynchronous for TagGui {
type Error = CrudError;
fn check_for_asynchronous_validity_response(&mut self) {
}
fn is_valid_asynchronous(&self) -> Option<Result<(), Self::Error>> {
Some(Ok(()))
}
fn trigger_asynchronous_validity_update(&mut self) {
}
}
impl Valid for TagGui {}
impl ErrorStyle for TagGui {}
impl ToOpenTimelineType<Tag> for TagGui {
fn to_opentimeline_type(&self) -> Tag {
let tag_name = (!self.name.trim().is_empty()).then(|| TagName::from(&self.name).unwrap());
let tag_value = TagValue::from(&self.value).unwrap();
Tag::from(tag_name, tag_value)
}
}
impl Draw for TagGui {
fn draw(&mut self, ctx: &Context, ui: &mut Ui) {
self.check_for_asynchronous_validity_response();
let spacing = widget_x_spacing(ui);
let row_height = body_text_height(ui);
let available_width = match self.show_remove_button {
ShowRemoveButton::Yes => ui.available_width() - REMOVE_BUTTON_WIDTH - (spacing * 2.0),
ShowRemoveButton::No => ui.available_width() - spacing,
};
let tag_component_input_width = available_width / 2.0;
let tag_component_input_size = [tag_component_input_width, row_height];
ui.horizontal(|ui| {
let (name_input, value_input) = ui
.scope(|ui| {
self.set_validity_styling(ctx, ui);
let name_input = ui.add_sized(
tag_component_input_size,
TextEdit::singleline(&mut self.name),
);
let value_input = ui.add_sized(
tag_component_input_size,
TextEdit::singleline(&mut self.value),
);
{
if (name_input.lost_focus() || value_input.lost_focus())
&& !(name_input.gained_focus() || value_input.gained_focus())
{
self.update_validity();
}
if name_input.changed() || value_input.changed() {
self.update_validity();
}
}
(name_input, value_input)
})
.inner;
if self.show_remove_button == ShowRemoveButton::Yes
&& open_timeline_gui_core::Button::remove(ui).clicked()
{
self.requested_action = Some(RequestedAction::Remove);
}
{
if name_input.has_focus() && keyboard_input_cmd_and_enter(ctx) {
self.requested_action =
Some(RequestedAction::AddNew(TagFocusRequestTarget::Name));
}
if value_input.has_focus() && keyboard_input_cmd_and_enter(ctx) {
self.requested_action =
Some(RequestedAction::AddNew(TagFocusRequestTarget::Value));
}
if (value_input.has_focus() || name_input.has_focus())
&& keyboard_input_cmd_and_k(ctx)
{
self.requested_action = Some(RequestedAction::Remove);
}
}
if let Some(tag_component_focus_target) = self.component_to_focus_on.take() {
match tag_component_focus_target {
TagFocusRequestTarget::Name => name_input.request_focus(),
TagFocusRequestTarget::Value => value_input.request_focus(),
}
}
});
}
}