use std::convert::TryFrom;
use std::sync::LazyLock;
use crate::TreeHelper;
use crate::tree::{Tree, NodeId};
use crate::interface::{Action, Element, Model, View};
use crate::interface::view::{self, input};
use crate::interface::controller::{self, controls, Controls};
use super::{Widget, Frame};
pub use self::builder::FieldBuilder as Builder;
pub type Field <'element> = Widget <'element,
controller::component::Cursor, String, view::component::Body>;
pub static CONTROLS : LazyLock <Controls> = LazyLock::new (||
controls::Builder::new()
.buttons (vec![
controls::button::Builtin::FieldBackspace,
controls::button::Builtin::FormSubmitCallback
].into_iter().map (Into::into).collect::<Vec <_>>().into())
.build()
);
pub fn line_entry (
input : &input::Text,
elements : &Tree <Element>,
node_id : &NodeId,
action_buffer : &mut Vec <(NodeId, Action)>
) {
use std::convert::TryInto;
log::trace!("line_entry...");
let Widget (cursor, text, _) = Field::try_get (elements, node_id).unwrap();
if let Some (new_text) = match input {
input::Text::Char (ch) => match ch {
'\t' | '\n' => None,
c if c.is_ascii_control() => None,
c => if cursor.length.is_none_or (|length| (text.len() as u32) < length) {
let pushchar = || {
let mut text = text.clone();
text.push (*ch);
Some (text)
};
if let Ok (keycode) = (*c).try_into() {
if cursor.ignore.contains (&keycode) {
None
} else {
pushchar()
}
} else {
pushchar()
}
} else {
None
}
}
input::Text::String (string) => {
let remaining = cursor.length.map_or_else (|| string.len(),
|length| (length as usize).saturating_sub (text.len()));
if remaining > 0 {
let mut text = text.clone();
text.extend (string.chars().take (remaining));
Some (text)
} else {
None
}
}
} {
set_contents (elements, node_id, new_text, None, action_buffer);
}
log::trace!("...line_entry");
}
pub fn backspace (
_ : &controls::button::Release,
elements : &Tree <Element>,
node_id : &NodeId,
action_buffer : &mut Vec <(NodeId, Action)>
) {
log::trace!("backspace...");
let Widget (_, text, _) = Field::try_get (elements, node_id).unwrap();
if text.len() > 0 {
let mut new_text = text.clone();
let _ = new_text.pop().unwrap();
set_contents (elements, node_id, new_text, None, action_buffer);
}
log::trace!("...backspace");
}
pub fn set_contents (
elements : &Tree <Element>,
field_id : &NodeId,
contents : String,
focused : Option <bool>,
action_buffer : &mut Vec <(NodeId, Action)>
) {
let new_body = {
let field = elements.get_element (field_id);
let Widget (cursor, _, _) = Field::try_get (elements, field_id).unwrap();
let Widget (_, _, canvas) =
Frame::try_get (elements, elements.get_parent_id (field_id)).unwrap();
let focused = focused
.unwrap_or (field.controller.state == controller::State::Focused);
body (canvas, cursor, &contents, focused)
};
{ let update_text = Box::new (|model : &mut Model| model.component = contents.into());
action_buffer.push ((field_id.clone(), Action::ModifyModel (update_text)));
}
{ let update_body = Box::new (|view : &mut View| view.component = new_body.into());
action_buffer.push ((field_id.clone(), Action::ModifyView (update_body)));
}
}
pub (crate) fn body (
canvas : &view::component::Canvas,
cursor : &controller::component::Cursor,
text : &str,
focused : bool
) -> view::component::Body {
log::trace!("body...");
let body = {
let mut body = {
let (body_width, _body_height) = canvas.body_wh();
let end = text.len();
let begin = {
let mut begin = text.len().saturating_sub (body_width as usize-1);
if !focused {
begin = begin.saturating_sub (1);
}
begin
};
text[begin..end].to_string()
};
if focused {
let caret = char::try_from (cursor.caret).unwrap();
body.push (caret);
}
view::component::Body (body)
};
log::trace!("...body");
body
}
mod builder {
use derive_builder::Builder;
use crate::prelude::*;
#[derive(Builder)]
#[builder(public, pattern="owned", build_fn(private), setter(strip_option))]
struct Field <'a, A : Application> {
elements : &'a Tree <Element>,
parent_id : &'a NodeId,
#[builder(default)]
appearances : Appearances,
#[builder(default)]
bindings : Option <&'a Bindings <A>>,
#[builder(default)]
callback_id : Option <application::CallbackId>,
#[builder(default)]
contents : Option <String>,
#[builder(default)]
cursor : Cursor,
#[builder(default)]
frame_anchor : Alignment,
#[builder(default)]
frame_appearances : Appearances,
#[builder(default)]
frame_area : Area,
#[builder(default)]
frame_bindings : Option <&'a Bindings <A>>,
#[builder(default)]
frame_border : Option <Border>,
#[builder(default)]
frame_clear_color : Option <canvas::ClearColor>,
#[builder(default)]
frame_offset : Offset,
#[builder(default)]
frame_tiled : bool,
#[builder(default = "size::Unsigned::Absolute (24)")]
frame_width : size::Unsigned,
#[builder(default)]
name : Option <String>,
#[builder(default)]
text_control : Option <controls::Text>
}
impl <'a, A : Application> FieldBuilder <'a, A> {
pub const fn new (elements : &'a Tree <Element>, parent_id : &'a NodeId) -> Self {
FieldBuilder {
elements: Some (elements),
parent_id: Some (parent_id),
appearances: None,
bindings: None,
callback_id: None,
contents: None,
cursor: None,
frame_anchor: None,
frame_appearances: None,
frame_area: None,
frame_bindings: None,
frame_border: None,
frame_clear_color: None,
frame_offset: None,
frame_tiled: None,
frame_width: None,
name: None,
text_control: None
}
}
}
impl <A : Application> BuildActions for FieldBuilder <'_, A> {
fn build_actions (self) -> Vec<(NodeId, Action)> {
use std::convert::TryInto;
use crate::tree::{InsertBehavior, Node};
use controller::component::layout;
use view::coordinates;
log::trace!("build actions...");
let Field {
elements, parent_id, appearances, bindings, callback_id, contents, cursor,
frame_anchor, frame_appearances, frame_area, frame_bindings, frame_border,
frame_clear_color, frame_offset, frame_tiled, frame_width, name, text_control
} = self.build()
.map_err(|err| log::error!("frame builder error: {err:?}")).unwrap();
let bindings_empty = Bindings::empty();
let bindings = {
let mut bindings = bindings.unwrap_or (&bindings_empty)
.get_bindings (&super::CONTROLS);
bindings.text = text_control.map (Into::into)
.or (Some (controls::text::Builtin::FieldLineEntry.into()));
bindings
};
let frame_bindings = frame_bindings.unwrap_or (&bindings_empty);
{ let Widget (_, _, canvas) = Frame::try_get (elements, parent_id).unwrap();
assert!(canvas.coordinates.kind() == coordinates::Kind::Tile);
}
let mut out = vec![];
let (mut subtree, order) = {
let (_, border_h) = frame_border.as_ref().map_or ((0, 0), Border::total_wh);
let height = border_h as u32 + 1;
let layout = if frame_tiled {
layout::Variant::from (layout::Tiled::Absolute (
std::num::NonZeroU32::new (height).unwrap()))
} else {
let anchor = frame_anchor;
let offset = frame_offset;
let size = {
let width = frame_width;
let height = height.into();
Size { width, height }
};
layout::Variant::from ((layout::Free { anchor, offset, size }, frame_area))
};
let mut actions = {
let mut frame = frame::Builder::new (elements, parent_id)
.appearances (frame_appearances)
.bindings (frame_bindings)
.layout (layout.into());
set_option!(frame, border, frame_border);
set_option!(frame, clear_color, frame_clear_color);
frame.build_actions()
};
out.extend (actions.drain (1..));
debug_assert_eq!(actions.len(), 1);
actions.pop().unwrap().1.try_into().unwrap()
};
let frame_id = subtree.root_node_id().unwrap().clone();
let field = {
let contents = contents.unwrap_or_else (String::new);
let controller = {
let component = cursor.clone().into();
let mut controller = Controller::with_bindings (&bindings);
controller.component = component;
controller.appearances = appearances;
controller
};
let model = Model { callback_id, component: contents.clone().into() };
let view = {
let Widget (_, _, canvas) = Frame::try_get (&subtree, &frame_id).unwrap();
view::Component::from (super::body (canvas, &cursor, &contents, false)).into()
};
let name = name.unwrap_or ("Field".to_string());
Element::new (name, controller, model, view)
};
let _ = subtree.insert (Node::new (field), InsertBehavior::UnderNode (&frame_id))
.unwrap();
out.push ((parent_id.clone(), Action::Create (subtree, order)));
log::trace!("...build actions");
out
}
}
}