pub struct View<M = ()> { /* private fields */ }Expand description
A surface’s view: the root of each region (main, dock, layer).
A region left out is not shown.
A node given for a region is the region root itself; a list (a Vec or
an array) is wrapped in a col root. Tern draws a region’s children into
an element of its own and ignores the root’s kind and props (but
role), so a root should be a container. A bare node or list converts
into a view as main’s children:
use tern_sdk::ui::{self, View};
let view: View = ui::md("**hi**").into();
let root = view.regions()[0].unwrap();
assert_eq!((root.kind(), root.child_nodes()[0].kind()), ("col", "md"));Implementations§
Source§impl<M> View<M>
impl<M> View<M>
Sourcepub fn plain(&self, cols: usize) -> String
pub fn plain(&self, cols: usize) -> String
The view as plain text cols wide: what a program prints outside
Tern. Works for views of any message type (see plain).
Examples found in repository?
1121fn main() -> Result<(), tern_sdk::Error> {
1122 let mut dash = Dash::new();
1123 let Some(mut session) = Session::connect(Options::new().app("dashboard"))? else {
1124 print!("{}", dash.view(true).plain(100));
1125 return Ok(());
1126 };
1127 let sf = session.open(SurfaceOptions::screen().title("Fleet").font(Font::Sans))?;
1128 session.render(sf, dash.view(false))?;
1129 let mut next = Instant::now() + TICK;
1130 loop {
1131 let left = next.saturating_duration_since(Instant::now());
1132 match session.next(Some(left))? {
1133 None => {
1134 dash.tick();
1135 next = Instant::now() + TICK;
1136 },
1137 Some(Input::Msg(msg, _)) => dash.update(msg),
1138 Some(Input::Key(key)) => {
1139 if key.is("escape") && dash.confirming.is_some() {
1140 dash.update(Msg::Keep);
1141 } else if key.is("enter") && dash.confirming.is_some() {
1142 dash.update(Msg::Confirm);
1143 } else if key.is("escape") || key.is("q") || key.is("ctrl+c") {
1144 break;
1145 } else if key.is("tab") || key.is("]") {
1146 dash.page = dash.page.turn(false);
1147 } else if key.is("shift+tab") || key.is("[") {
1148 dash.page = dash.page.turn(true);
1149 } else if key.is("up") || key.is("k") {
1150 dash.pick(dash.page, -1);
1151 } else if key.is("down") || key.is("j") {
1152 dash.pick(dash.page, 1);
1153 } else if key.is("r") {
1154 dash.update(Msg::Refresh);
1155 } else if key.is("a") {
1156 dash.act_selected();
1157 } else if key.is("enter") {
1158 if let Some(id) = dash.selected[dash.page.index()] {
1159 dash.update(Msg::Open(dash.page, id.into()));
1160 }
1161 } else {
1162 continue;
1163 }
1164 },
1165 Some(Input::Event(_) | Input::Clipboard(_)) => continue,
1166 }
1167 session.render(sf, dash.view(false))?;
1168 }
1169 session.close()?;
1170 Ok(())
1171}Source§impl<M> View<M>
impl<M> View<M>
Sourcepub fn new() -> Self
pub fn new() -> Self
The empty view.
Examples found in repository?
More examples
28fn view() -> View<()> {
29 let sizes = SIZES.iter().map(|(value, label)| choice(value, label));
30 View::new().main([html::form().key("ask").child(
31 ui::card()
32 .key("card")
33 .head("Which size?")
34 .max(Bound::w(Extent::Ch(48.0)))
35 .child(ui::row().key("sizes").gap("lg").children(sizes))
36 .child(
37 ui::button("Create")
38 .key("go")
39 .variant("primary")
40 .actions(Actions::new().click("submit")),
41 ),
42 )])
43}43fn view(entries: &[Entry]) -> View {
44 let rows = entries.iter().map(|e| {
45 let (name, icon) = if e.dir {
46 (format!("{}/", e.name), "folder")
47 } else {
48 (e.name.clone(), "file")
49 };
50 let size = if e.dir { "-".to_owned() } else { human(e.size) };
51 TableRow::new(e.name.clone())
52 .cell("name", TextCell::new(name).icon(icon))
53 .cell("size", size)
54 });
55 View::new().main([ui::table()
56 .col(Column::new("name", "Name").grow(1.0))
57 .col(Column::new("size", "Size").align(TextAlign::End).mono(true))
58 .rows(rows.collect::<Vec<_>>())])
59}40fn view(build: &Build) -> View {
41 let fraction = build.done as f64 / STEPS.len() as f64;
42 let running = build.took.is_none();
43 let activity: ui::Node = if running {
44 ui::spinner()
45 .style("dots")
46 .label(STEPS[build.done.min(STEPS.len() - 1)])
47 .into()
48 } else {
49 ui::icon(if build.status == Status::Done {
50 "check"
51 } else {
52 "x"
53 })
54 .tone(if build.status == Status::Done {
55 "success"
56 } else {
57 "error"
58 })
59 .into()
60 };
61 let mut timer = ui::elapsed().age(0);
62 if let Some(took) = build.took {
63 timer = timer.stopped(took);
64 }
65 View::new().main([ui::card()
66 .key("build")
67 .head("Building demo")
68 .status(build.status.clone())
69 .child(
70 ui::row()
71 .key("head")
72 .gap("sm")
73 .children(nodes![activity.key("a"), timer.key("t")]),
74 )
75 .child(
76 ui::ansi(&build.log)
77 .key("log")
78 .follow(true)
79 .max(Bound::h(Extent::Lines(8.0))),
80 )
81 .child(ui::progress().key("bar").value(fraction).label(format!(
82 "{}/{}",
83 build.done,
84 STEPS.len()
85 )))])
86}62 fn view(&self) -> View<Msg> {
63 let messages = self.transcript.iter().enumerate().map(|(i, (who, text))| {
64 let node = ui::md(text.clone()).key(i);
65 if *who == "you" {
66 ui::card().key(i).tone("user").child(node.key("t"))
67 } else {
68 ui::card().key(i).variant("bare").child(node.key("t"))
69 }
70 });
71 let cursor: usize = self
72 .draft
73 .chars()
74 .take(self.cursor)
75 .map(char::len_utf16)
76 .sum();
77 View::new().main(ui::col().children(messages)).dock(nodes![
78 ui::editor()
79 .key("ed")
80 .text(self.draft.clone())
81 .cursor(cursor)
82 .placeholder("Message (Enter sends, Escape quits)")
83 .sendable(true)
84 .on_send(|text| Msg::Send(text.to_owned()))
85 .on_focus(Msg::Focus),
86 ui::status()
87 .key("bar")
88 .child(ui::seg("chat").icon("message"))
89 .child(
90 ui::seg(format!("{} messages", self.transcript.len()))
91 .side(Side::Right)
92 .tone(if self.transcript.is_empty() {
93 Tone::Muted
94 } else {
95 Tone::Success
96 }),
97 ),
98 ])
99 }Sourcepub fn main(self, region: impl Into<Node<M>>) -> Self
pub fn main(self, region: impl Into<Node<M>>) -> Self
Sets main, the flowing document: a root node, or a list of
children wrapped in a col.
Examples found in repository?
More examples
28fn view() -> View<()> {
29 let sizes = SIZES.iter().map(|(value, label)| choice(value, label));
30 View::new().main([html::form().key("ask").child(
31 ui::card()
32 .key("card")
33 .head("Which size?")
34 .max(Bound::w(Extent::Ch(48.0)))
35 .child(ui::row().key("sizes").gap("lg").children(sizes))
36 .child(
37 ui::button("Create")
38 .key("go")
39 .variant("primary")
40 .actions(Actions::new().click("submit")),
41 ),
42 )])
43}43fn view(entries: &[Entry]) -> View {
44 let rows = entries.iter().map(|e| {
45 let (name, icon) = if e.dir {
46 (format!("{}/", e.name), "folder")
47 } else {
48 (e.name.clone(), "file")
49 };
50 let size = if e.dir { "-".to_owned() } else { human(e.size) };
51 TableRow::new(e.name.clone())
52 .cell("name", TextCell::new(name).icon(icon))
53 .cell("size", size)
54 });
55 View::new().main([ui::table()
56 .col(Column::new("name", "Name").grow(1.0))
57 .col(Column::new("size", "Size").align(TextAlign::End).mono(true))
58 .rows(rows.collect::<Vec<_>>())])
59}40fn view(build: &Build) -> View {
41 let fraction = build.done as f64 / STEPS.len() as f64;
42 let running = build.took.is_none();
43 let activity: ui::Node = if running {
44 ui::spinner()
45 .style("dots")
46 .label(STEPS[build.done.min(STEPS.len() - 1)])
47 .into()
48 } else {
49 ui::icon(if build.status == Status::Done {
50 "check"
51 } else {
52 "x"
53 })
54 .tone(if build.status == Status::Done {
55 "success"
56 } else {
57 "error"
58 })
59 .into()
60 };
61 let mut timer = ui::elapsed().age(0);
62 if let Some(took) = build.took {
63 timer = timer.stopped(took);
64 }
65 View::new().main([ui::card()
66 .key("build")
67 .head("Building demo")
68 .status(build.status.clone())
69 .child(
70 ui::row()
71 .key("head")
72 .gap("sm")
73 .children(nodes![activity.key("a"), timer.key("t")]),
74 )
75 .child(
76 ui::ansi(&build.log)
77 .key("log")
78 .follow(true)
79 .max(Bound::h(Extent::Lines(8.0))),
80 )
81 .child(ui::progress().key("bar").value(fraction).label(format!(
82 "{}/{}",
83 build.done,
84 STEPS.len()
85 )))])
86}62 fn view(&self) -> View<Msg> {
63 let messages = self.transcript.iter().enumerate().map(|(i, (who, text))| {
64 let node = ui::md(text.clone()).key(i);
65 if *who == "you" {
66 ui::card().key(i).tone("user").child(node.key("t"))
67 } else {
68 ui::card().key(i).variant("bare").child(node.key("t"))
69 }
70 });
71 let cursor: usize = self
72 .draft
73 .chars()
74 .take(self.cursor)
75 .map(char::len_utf16)
76 .sum();
77 View::new().main(ui::col().children(messages)).dock(nodes![
78 ui::editor()
79 .key("ed")
80 .text(self.draft.clone())
81 .cursor(cursor)
82 .placeholder("Message (Enter sends, Escape quits)")
83 .sendable(true)
84 .on_send(|text| Msg::Send(text.to_owned()))
85 .on_focus(Msg::Focus),
86 ui::status()
87 .key("bar")
88 .child(ui::seg("chat").icon("message"))
89 .child(
90 ui::seg(format!("{} messages", self.transcript.len()))
91 .side(Side::Right)
92 .tone(if self.transcript.is_empty() {
93 Tone::Muted
94 } else {
95 Tone::Success
96 }),
97 ),
98 ])
99 }Sourcepub fn dock(self, region: impl Into<Node<M>>) -> Self
pub fn dock(self, region: impl Into<Node<M>>) -> Self
Sets dock, sticky at the pane’s bottom: a root node, or a list.
Examples found in repository?
More examples
62 fn view(&self) -> View<Msg> {
63 let messages = self.transcript.iter().enumerate().map(|(i, (who, text))| {
64 let node = ui::md(text.clone()).key(i);
65 if *who == "you" {
66 ui::card().key(i).tone("user").child(node.key("t"))
67 } else {
68 ui::card().key(i).variant("bare").child(node.key("t"))
69 }
70 });
71 let cursor: usize = self
72 .draft
73 .chars()
74 .take(self.cursor)
75 .map(char::len_utf16)
76 .sum();
77 View::new().main(ui::col().children(messages)).dock(nodes![
78 ui::editor()
79 .key("ed")
80 .text(self.draft.clone())
81 .cursor(cursor)
82 .placeholder("Message (Enter sends, Escape quits)")
83 .sendable(true)
84 .on_send(|text| Msg::Send(text.to_owned()))
85 .on_focus(Msg::Focus),
86 ui::status()
87 .key("bar")
88 .child(ui::seg("chat").icon("message"))
89 .child(
90 ui::seg(format!("{} messages", self.transcript.len()))
91 .side(Side::Right)
92 .tone(if self.transcript.is_empty() {
93 Tone::Muted
94 } else {
95 Tone::Success
96 }),
97 ),
98 ])
99 }Trait Implementations§
Source§impl<M> From<Attachment<M>> for View<M>
A bare node is main’s one child.
impl<M> From<Attachment<M>> for View<M>
A bare node is main’s one child.
Source§fn from(n: Attachment<M>) -> Self
fn from(n: Attachment<M>) -> Self
Source§impl<M> From<Breadcrumb<M>> for View<M>
A bare node is main’s one child.
impl<M> From<Breadcrumb<M>> for View<M>
A bare node is main’s one child.
Source§fn from(n: Breadcrumb<M>) -> Self
fn from(n: Breadcrumb<M>) -> Self
Source§impl<M> From<Stacktrace<M>> for View<M>
A bare node is main’s one child.
impl<M> From<Stacktrace<M>> for View<M>
A bare node is main’s one child.
Source§fn from(n: Stacktrace<M>) -> Self
fn from(n: Stacktrace<M>) -> Self
Source§impl<M> From<Suggestions<M>> for View<M>
A bare node is main’s one child.
impl<M> From<Suggestions<M>> for View<M>
A bare node is main’s one child.