diff --git a/dsl/src/dsl.rs b/dsl/src/dsl.rs index c6c4747..e9d108b 100644 --- a/dsl/src/dsl.rs +++ b/dsl/src/dsl.rs @@ -17,12 +17,14 @@ mod dsl_conv; pub use self::dsl_conv::*; pub type DslResult = Result; /// DSL-specific optional result type. pub type DslPerhaps = Result, DslError>; +// Some things that can be DSL source: +impl Dsl for String { fn src (&self) -> DslPerhaps<&str> { Ok(Some(self.as_ref())) } } +impl Dsl for Arc { fn src (&self) -> DslPerhaps<&str> { Ok(Some(self.as_ref())) } } +impl<'s> Dsl for &'s str { fn src (&self) -> DslPerhaps<&str> { Ok(Some(self.as_ref())) } } // Designates a string as parsable DSL. flex_trait!(Dsl: Debug + Send + Sync + Sized { fn src (&self) -> DslPerhaps<&str> { unreachable!("Dsl::src default impl") } }); -impl Dsl for Arc { fn src (&self) -> DslPerhaps<&str> { Ok(Some(self.as_ref())) } } -impl<'s> Dsl for &'s str { fn src (&self) -> DslPerhaps<&str> { Ok(Some(self.as_ref())) } } impl Dsl for Option { fn src (&self) -> DslPerhaps<&str> {Ok(if let Some(dsl) = self { dsl.src()? } else { None })} } @@ -45,12 +47,11 @@ impl DslExp for D {} pub trait DslExp: Dsl { fn exp (&self) -> DslPerhaps<&str> {ok_flat(self.src()?.map(exp_peek_inner_only))} fn head (&self) -> DslPerhaps<&str> {ok_flat(self.src()?.map(peek))} fn tail (&self) -> DslPerhaps<&str> {ok_flat(self.src()?.map(peek_tail))} + /// my other car is a cdr :< fn each (&self, mut cb: impl FnMut(&str)->Usually<()>) -> Usually<()> { Ok(if let Some(head) = self.head()? { - println!("| HEAD ->\n{head}"); cb(head)?; if let Some(tail) = self.tail()? { - println!("| TAIL ->\n{tail}"); tail.each(cb)?; } }) @@ -194,7 +195,7 @@ pub const fn exp_seek_length (source: &str) -> DslPerhaps { } def_peek_seek!(sym_peek, sym_peek_only, sym_seek, sym_seek_start, sym_seek_length); pub const fn is_sym_start (c: char) -> bool { matches!(c, ':'|'@') } -pub const fn is_sym_char (c: char) -> bool { matches!(c, ':'|'a'..='z'|'A'..='Z'|'0'..='9'|'-'|'/') } +pub const fn is_sym_char (c: char) -> bool { is_sym_start(c) || matches!(c, 'a'..='z'|'A'..='Z'|'0'..='9'|'-'|'/') } pub const fn is_sym_end (c: char) -> bool { is_space(c) || matches!(c, ')') } pub const fn sym_seek_start (source: &str) -> DslPerhaps { for_each!((i, c) in char_indices(source) => if is_sym_start(c) { diff --git a/dsl/src/dsl_conv.rs b/dsl/src/dsl_conv.rs index 0600679..ae214dd 100644 --- a/dsl/src/dsl_conv.rs +++ b/dsl/src/dsl_conv.rs @@ -12,24 +12,24 @@ pub trait FromDsl: Sized { } /// `self` + [Dsl] -> `T` -pub trait DslInto<'s, T> { - fn dsl_into (&'s self, dsl: &impl Dsl) -> Perhaps; - fn dsl_into_or (&'s self, dsl: &impl Dsl, err: Box) -> Usually { +pub trait DslInto { + fn dsl_into (&self, dsl: &impl Dsl) -> Perhaps; + fn dsl_into_or (&self, dsl: &impl Dsl, err: Box) -> Usually { self.dsl_into(dsl)?.ok_or(err) } - fn dsl_into_or_else (&'s self, dsl: &impl Dsl, err: impl Fn()->Box) -> Usually { + fn dsl_into_or_else (&self, dsl: &impl Dsl, err: impl Fn()->Box) -> Usually { self.dsl_into(dsl)?.ok_or_else(err) } } /// `self` + `T` -> [Dsl] pub trait DslFrom { - fn dsl_from (&self, dsl: &T) -> Perhaps; - fn dsl_from_or (&self, dsl: &T, err: Box) -> Usually { - self.dsl_from(dsl)?.ok_or(err) + fn dsl_from (&self, val: &T) -> Perhaps; + fn dsl_from_or (&self, val: &T, err: Box) -> Usually { + self.dsl_from(val)?.ok_or(err) } - fn dsl_from_or_else (&self, dsl: &T, err: impl Fn()->Box) -> Usually { - self.dsl_from(dsl)?.ok_or_else(err) + fn dsl_from_or_else (&self, val: &T, err: impl Fn()->Box) -> Usually { + self.dsl_from(val)?.ok_or_else(err) } } @@ -44,14 +44,14 @@ pub trait IntoDsl { } } -impl<'s, T: FromDsl, U> DslInto<'s, T> for U { - fn dsl_into (&self, dsl: &impl Dsl) -> Perhaps { - T::from_dsl(self, dsl) - } -} +//impl<'s, T: FromDsl, U> DslInto<'s, T> for U { + //fn dsl_into (&self, dsl: &impl Dsl) -> Perhaps { + //T::from_dsl(self, dsl) + //} +//} -impl, U> IntoDsl for U { - fn into_dsl (&self, state: &T) -> Perhaps { - T::dsl_from(state, self) - } -} +//impl, U> IntoDsl for U { + //fn into_dsl (&self, state: &T) -> Perhaps { + //T::dsl_from(state, self) + //} +//} diff --git a/input/src/input_dsl.rs b/input/src/input_dsl.rs index 1f6ac42..cc54282 100644 --- a/input/src/input_dsl.rs +++ b/input/src/input_dsl.rs @@ -14,7 +14,7 @@ type EventMapImpl = BTreeMap>>; /// When the first non-conditional or true conditional binding is executed, /// that .event()binding's value is returned. #[derive(Debug)] -pub struct EventMap(EventMapImpl); +pub struct EventMap(pub EventMapImpl); /// An input binding. #[derive(Debug, Clone)] pub struct Binding { @@ -60,59 +60,63 @@ impl EventMap { .flatten() } /// Create event map from path to text file. - pub fn from_path > (path: P) -> Usually where E: From> { + pub fn load_from_path <'s> ( + &'s mut self, path: impl AsRef + ) -> Usually<&mut Self> where Self: DslInto + DslInto { if exists(path.as_ref())? { - Self::from_source(read_to_string(path)?) + let source = read_to_string(&path)?; + let path: Arc = Arc::new(path.as_ref().into()); + self.load_from_source(&source, &Some(&path)) } else { return Err(format!("(e5) not found: {:?}", path.as_ref()).into()) } } - /// Create event map from string. - pub fn from_source (source: impl AsRef) -> Usually where E: From> { - Self::from_dsl(&mut source.as_ref()) - } /// Create event map from DSL tokenizer. - pub fn from_dsl <'s, > (dsl: &'s mut impl Dsl) -> Usually where E: From> { - let mut map: Self = Default::default(); - if let Some(dsl) = dsl.exp()? { - let mut head = dsl.head()?; - let mut tail = dsl.tail()?; - loop { - if let Some(ref token) = head { - if let Some(ref text) = token.text()? { - map.0.extend(Self::from_path(PathBuf::from(text))?.0); - continue - } - //if let Some(ref exp) = token.exp()? { - ////_ if let Some(sym) = token.head()?.sym()?.as_ref() => { - ////todo!() - ////}, - ////_ if Some(&"if") == token.head()?.key()?.as_ref() => { - ////todo!() - ////}, - //return Err(format!("unexpected: {:?}", token.exp()).into()) - //} - return Err(format!("unexpected: {token:?}").into()) - } else { - break - } - if let Some(next) = tail { - head = next.head()?; - tail = next.tail()?; - } else { - break - } - } - } else if let Some(text) = dsl.text()? { - todo!("load from file path") - } else { - todo!("return error for invalid input") - } - Ok(map) + pub fn load_from_source ( + &mut self, dsl: impl Dsl, path: &Option<&Arc> + ) -> Usually<&mut Self> where Self: DslInto + DslInto { + dsl.each(|dsl|self.load_from_source_one(&dsl, path).map(move|_|()))?; + Ok(self) } + /// Load one event binding into the event map. + pub fn load_from_source_one <'s> ( + &'s mut self, dsl: impl Dsl, path: &Option<&Arc> + ) -> Usually<&mut Self> where Self: DslInto + DslInto { + if let Some(exp) = dsl.head()?.exp()? + && let Some(sym) = exp.head()?.sym()? + && let Some(tail) = exp.tail()? + { + let event = self.dsl_into_or_else(&sym, ||panic!())?; + let command = self.dsl_into_or_else(&tail, ||panic!())?; + Ok(self.add(event, Binding { + command, condition: None, description: None, source: path.cloned() + })) + } else { + Err(format!("unexpected: {:?}", dsl.head()?).into()) + } + } + //})Ok(if let Some(sym) = dsl.head()?.exp()?.head()?.sym()? { + //if let Some(tail) = dsl.head()?.exp()?.tail()? { + //let event: E = sym.into(); + //let binding: Binding = Binding { command: tail.into(), condition: None, description: None, source: None }; + //if let Some(bindings) = map.0.get_mut(&event) { + //bindings.push(binding); + //} else { + //map.0.insert(event, vec![binding]); + //} + //} else { + //panic!("empty binding: {}", dsl.head()?.exp()?.unwrap_or_default()) + //} + //} else if let Some(ref text) = dsl.text()? { + //map.0.extend(Self::from_path(PathBuf::from(text))?.0); + //} else { + //return Err(format!("unexpected: {dsl:?}").into()) + //})); + //Ok(map) + //} } impl Binding { - fn from_dsl (dsl: impl Dsl) -> Usually { + pub fn from_dsl (dsl: impl Dsl) -> Usually { let mut command: Option = None; let mut condition: Option = None; let mut description: Option> = None; diff --git a/output/src/output.rs b/output/src/output.rs index 07c8ac2..77a48b8 100644 --- a/output/src/output.rs +++ b/output/src/output.rs @@ -14,7 +14,7 @@ pub trait Output: Send + Sync + Sized { /// Mutable pointer to area fn area_mut (&mut self) -> &mut Self::Area; /// Render widget in area - fn place + ?Sized> (&mut self, area: Self::Area, content: &T); + fn place <'t, T: Render + ?Sized> (&mut self, area: Self::Area, content: &'t T); #[inline] fn x (&self) -> Self::Unit { self.area().x() } #[inline] fn y (&self) -> Self::Unit { self.area().y() } #[inline] fn w (&self) -> Self::Unit { self.area().w() } diff --git a/proc/src/proc_view.rs b/proc/src/proc_view.rs index 13a5d5c..7a70b2e 100644 --- a/proc/src/proc_view.rs +++ b/proc/src/proc_view.rs @@ -7,25 +7,26 @@ pub(crate) struct ViewDef(pub(crate) ViewMeta, pub(crate) ViewImpl); pub(crate) struct ViewMeta { pub(crate) output: Ident } #[derive(Debug, Clone)] -pub(crate) struct ViewImpl { - block: ItemImpl, - exposed: BTreeMap, -} +pub(crate) struct ViewImpl { block: ItemImpl, exposed: BTreeMap, } +/// `#[view]` takes 1 arg ([Engine] implementation for which it's valid). impl Parse for ViewMeta { fn parse (input: ParseStream) -> Result { - Ok(Self { - output: input.parse::()?, - }) + Ok(Self { output: input.parse::()?, }) } } +/// `#[view]` exposes each function as corresponding symbol. +/// +/// Maybe it's best to genericize that pattern rather than have +/// 3 whole things for the layers of the program. impl Parse for ViewImpl { fn parse (input: ParseStream) -> Result { let block = input.parse::()?; let mut exposed: BTreeMap = Default::default(); for item in block.items.iter() { - if let ImplItem::Fn(ImplItemFn { sig: Signature { ident, .. }, .. }) = item { + if let ImplItem::Fn(ImplItemFn { sig: Signature { ident, inputs, .. }, .. }) = item + && inputs.len() == 1 { let key = format!(":{}", AsKebabCase(format!("{}", &ident))); if exposed.contains_key(&key) { return Err(input.error(format!("already defined: {ident}"))); @@ -37,6 +38,7 @@ impl Parse for ViewImpl { } } +/// `#[view]`'s output is a generated block of symbol bindings. impl ToTokens for ViewDef { fn to_tokens (&self, out: &mut TokenStream2) { let Self(_, ViewImpl { block, .. }) = self; @@ -78,10 +80,8 @@ impl ViewDef { /// Makes [#self_ty] able to construct the [Render]able /// which might correspond to a given [TokenStream], /// while taking [#self_ty]'s state into consideration. - impl<'state> ::tengri::dsl::DslInto<'state, - Box + 'state> - > for #self_ty { - fn dsl_into (&'state self, dsl: &impl ::tengri::dsl::Dsl) + impl<'state> ::tengri::dsl::DslInto + 'state>> for #self_ty { + fn dsl_into (&self, dsl: &impl ::tengri::dsl::Dsl) -> Perhaps + 'state>> { #(#builtins)* diff --git a/tui/src/lib.rs b/tui/src/lib.rs index 50d7eeb..8ed9006 100644 --- a/tui/src/lib.rs +++ b/tui/src/lib.rs @@ -2,6 +2,7 @@ mod tui_engine; pub use self::tui_engine::*; mod tui_content; pub use self::tui_content::*; pub(crate) use ::tengri_core::*; +#[cfg(feature = "dsl")] pub use ::tengri_dsl::*; pub use ::tengri_input as input; pub(crate) use ::tengri_input::*; pub use ::tengri_output as output; pub(crate) use ::tengri_output::*; pub(crate) use atomic_float::AtomicF64; diff --git a/tui/src/tui_engine.rs b/tui/src/tui_engine.rs index 9cd527d..4700555 100644 --- a/tui/src/tui_engine.rs +++ b/tui/src/tui_engine.rs @@ -3,6 +3,7 @@ use std::time::Duration; mod tui_buffer; pub use self::tui_buffer::*; mod tui_input; pub use self::tui_input::*; +mod tui_event; pub use self::tui_event::*; mod tui_output; pub use self::tui_output::*; mod tui_perf; pub use self::tui_perf::*; diff --git a/tui/src/tui_engine/tui_event.rs b/tui/src/tui_engine/tui_event.rs new file mode 100644 index 0000000..93dd4ff --- /dev/null +++ b/tui/src/tui_engine/tui_event.rs @@ -0,0 +1,147 @@ +use crate::*; +#[derive(Debug, Clone, Eq, PartialEq, PartialOrd)] pub struct TuiEvent(Event); +impl Ord for TuiEvent { + fn cmp (&self, other: &Self) -> std::cmp::Ordering { + self.partial_cmp(other) + .unwrap_or_else(||format!("{:?}", self).cmp(&format!("{other:?}"))) // FIXME perf + } +} +impl TuiEvent { + pub fn from_crossterm (event: Event) -> Self { + Self(event) + } + pub fn from_dsl (dsl: impl Dsl) -> Perhaps { + Ok(TuiKey::from_dsl(dsl)?.to_crossterm().map(Self)) + } +} +pub struct TuiKey(Option, KeyModifiers); +impl TuiKey { + const SPLIT: char = '/'; + pub fn from_dsl (dsl: impl Dsl) -> Usually { + if let Some(symbol) = dsl.sym()? { + let symbol = symbol.trim(); + Ok(if symbol == ":char" { + Self(None, KeyModifiers::NONE) + } else if symbol.chars().nth(0) == Some('@') { + let mut key = None; + let mut modifiers = KeyModifiers::NONE; + let mut tokens = symbol[1..].split(Self::SPLIT).peekable(); + while let Some(token) = tokens.next() { + println!("{token}"); + if tokens.peek().is_some() { + match token { + "ctrl" | "Ctrl" | "c" | "C" => modifiers |= KeyModifiers::CONTROL, + "alt" | "Alt" | "m" | "M" => modifiers |= KeyModifiers::ALT, + "shift" | "Shift" | "s" | "S" => { + modifiers |= KeyModifiers::SHIFT; + // + TODO normalize character case, BackTab, etc. + }, + _ => panic!("unknown modifier {token}"), + } + } else { + key = if token.len() == 1 { + Some(KeyCode::Char(token.chars().next().unwrap())) + } else { + Some(named_key(token).unwrap_or_else(||panic!("unknown character {token}"))) + } + } + } + Self(key, modifiers) + } else { + return Err(format!("TuiKey: unexpected: {symbol}").into()) + }) + } else { + return Err(format!("TuiKey: unspecified").into()) + } + } + pub fn to_crossterm (&self) -> Option { + self.0.map(|code|Event::Key(KeyEvent { + code, + modifiers: self.1, + kind: KeyEventKind::Press, + state: KeyEventState::NONE, + })) + } +} +pub fn named_key (token: &str) -> Option { + use KeyCode::*; + Some(match token { + "up" => Up, + "down" => Down, + "left" => Left, + "right" => Right, + "esc" | "escape" => Esc, + "enter" | "return" => Enter, + "delete" | "del" => Delete, + "backspace" => Backspace, + "tab" => Tab, + "space" => Char(' '), + "comma" => Char(','), + "period" => Char('.'), + "plus" => Char('+'), + "minus" | "dash" => Char('-'), + "equal" | "equals" => Char('='), + "underscore" => Char('_'), + "backtick" => Char('`'), + "lt" => Char('<'), + "gt" => Char('>'), + "cbopen" | "openbrace" => Char('{'), + "cbclose" | "closebrace" => Char('}'), + "bropen" | "openbracket" => Char('['), + "brclose" | "closebracket" => Char(']'), + "pgup" | "pageup" => PageUp, + "pgdn" | "pagedown" => PageDown, + "f1" => F(1), + "f2" => F(2), + "f3" => F(3), + "f4" => F(4), + "f5" => F(5), + "f6" => F(6), + "f7" => F(7), + "f8" => F(8), + "f9" => F(9), + "f10" => F(10), + "f11" => F(11), + "f12" => F(12), + _ => return None, + }) +} + //let token = token.as_ref(); + //if token.len() < 2 { + //Self { valid: false, key: None, mods: KeyModifiers::NONE } + //} else if token.chars().next() != Some('@') { + //Self { valid: false, key: None, mods: KeyModifiers::NONE } + //} else { + //Self { valid: true, key: None, mods: KeyModifiers::NONE }.next(&token[1..]) + //} + //} + //pub fn build (self) -> Option { + //if self.valid && self.key.is_some() { + //Some(Event::Key(KeyEvent::new(self.key.unwrap(), self.mods))) + //} else { + //None + //} + //} + //fn next (mut self, token: &str) -> Self { + //let mut tokens = token.split('-').peekable(); + //while let Some(token) = tokens.next() { + //if tokens.peek().is_some() { + //match token { + //"ctrl" | "Ctrl" | "c" | "C" => self.mods |= KeyModifiers::CONTROL, + //"alt" | "Alt" | "m" | "M" => self.mods |= KeyModifiers::ALT, + //"shift" | "Shift" | "s" | "S" => { + //self.mods |= KeyModifiers::SHIFT; + //// + TODO normalize character case, BackTab, etc. + //}, + //_ => panic!("unknown modifier {token}"), + //} + //} else { + //self.key = if token.len() == 1 { + //Some(KeyCode::Char(token.chars().next().unwrap())) + //} else { + //Some(Self::named_key(token).unwrap_or_else(||panic!("unknown character {token}"))) + //} + //} + //} + //self + //} diff --git a/tui/src/tui_engine/tui_input.rs b/tui/src/tui_engine/tui_input.rs index 1d89f97..f42da7c 100644 --- a/tui/src/tui_engine/tui_input.rs +++ b/tui/src/tui_engine/tui_input.rs @@ -17,23 +17,6 @@ impl Input for TuiIn { fn is_done (&self) -> bool { self.exited.fetch_and(true, Relaxed) } fn done (&self) { self.exited.store(true, Relaxed); } } -#[derive(Debug, Clone, Eq, PartialEq, PartialOrd)] -pub struct TuiEvent(Event); -impl Ord for TuiEvent { - fn cmp (&self, other: &Self) -> std::cmp::Ordering { - self.partial_cmp(other) .unwrap_or_else(||format!("{:?}", self).cmp(&format!("{other:?}"))) // FIXME perf - } -} -impl From for TuiEvent { - fn from (event: Event) -> Self { - Self(event) - } -} -impl From> for TuiEvent { - fn from (x: Arc) -> Self { - TuiEvent(TuiKey::new(x.as_ref()).build().unwrap_or_else(||panic!("invalid key: {x}"))) - } -} impl TuiIn { /// Spawn the input thread. pub fn run_input + Send + Sync + 'static> ( @@ -60,7 +43,7 @@ impl TuiIn { }, _ => { let exited = exited.clone(); - let event = event.into(); + let event = TuiEvent::from_crossterm(event); if let Err(e) = state.write().unwrap().handle(&TuiIn { exited, event }) { panic!("{e}") } @@ -70,105 +53,3 @@ impl TuiIn { }) } } - -//#[cfg(feature = "dsl")] -//impl DslInput for TuiIn { - //fn matches_dsl (&self, token: &str) -> bool { - //if let Some(event) = TuiKey::new(token).build() { - //&event == self.event() - //} else { - //false - //} - //} -//} - -pub struct TuiKey { - valid: bool, - key: Option, - mods: KeyModifiers, -} - -impl TuiKey { - pub fn new (token: impl AsRef) -> Self { - let token = token.as_ref(); - if token.len() < 2 { - Self { valid: false, key: None, mods: KeyModifiers::NONE } - } else if token.chars().next() != Some('@') { - Self { valid: false, key: None, mods: KeyModifiers::NONE } - } else { - Self { valid: true, key: None, mods: KeyModifiers::NONE }.next(&token[1..]) - } - } - pub fn build (self) -> Option { - if self.valid && self.key.is_some() { - Some(Event::Key(KeyEvent::new(self.key.unwrap(), self.mods))) - } else { - None - } - } - fn next (mut self, token: &str) -> Self { - let mut tokens = token.split('-').peekable(); - while let Some(token) = tokens.next() { - if tokens.peek().is_some() { - match token { - "ctrl" | "Ctrl" | "c" | "C" => self.mods |= KeyModifiers::CONTROL, - "alt" | "Alt" | "m" | "M" => self.mods |= KeyModifiers::ALT, - "shift" | "Shift" | "s" | "S" => { - self.mods |= KeyModifiers::SHIFT; - // + TODO normalize character case, BackTab, etc. - }, - _ => panic!("unknown modifier {token}"), - } - } else { - self.key = if token.len() == 1 { - Some(KeyCode::Char(token.chars().next().unwrap())) - } else { - Some(Self::named_key(token).unwrap_or_else(||panic!("unknown character {token}"))) - } - } - } - self - } - fn named_key (token: &str) -> Option { - use KeyCode::*; - Some(match token { - "up" => Up, - "down" => Down, - "left" => Left, - "right" => Right, - "esc" | "escape" => Esc, - "enter" | "return" => Enter, - "delete" | "del" => Delete, - "tab" => Tab, - "space" => Char(' '), - "comma" => Char(','), - "period" => Char('.'), - "plus" => Char('+'), - "minus" | "dash" => Char('-'), - "equal" | "equals" => Char('='), - "underscore" => Char('_'), - "backtick" => Char('`'), - "lt" => Char('<'), - "gt" => Char('>'), - "cbopen" | "openbrace" => Char('{'), - "cbclose" | "closebrace" => Char('}'), - "bropen" | "openbracket" => Char('['), - "brclose" | "closebracket" => Char(']'), - "pgup" | "pageup" => PageUp, - "pgdn" | "pagedown" => PageDown, - "f1" => F(1), - "f2" => F(2), - "f3" => F(3), - "f4" => F(4), - "f5" => F(5), - "f6" => F(6), - "f7" => F(7), - "f8" => F(8), - "f9" => F(9), - "f10" => F(10), - "f11" => F(11), - "f12" => F(12), - _ => return None, - }) - } -} diff --git a/tui/src/tui_engine/tui_output.rs b/tui/src/tui_engine/tui_output.rs index 33b46ff..04cbe94 100644 --- a/tui/src/tui_engine/tui_output.rs +++ b/tui/src/tui_engine/tui_output.rs @@ -12,7 +12,7 @@ impl Output for TuiOut { type Area = [Self::Unit;4]; #[inline] fn area (&self) -> [u16;4] { self.area } #[inline] fn area_mut (&mut self) -> &mut [u16;4] { &mut self.area } - #[inline] fn place + ?Sized> (&mut self, area: [u16;4], content: &T) { + #[inline] fn place <'t, T: Render + ?Sized> (&mut self, area: [u16;4], content: &'t T) { let last = self.area(); *self.area_mut() = area; content.render(self);