//! The tabs example's widget tree -- the five demo panes plus the message //! composer that exercises `TextEdit`. Factored out of //! `iris/examples/tabs/main.rs` (I2, RUST.md) so the same UI runs under //! both backends: the winit example binary calls `build` from //! `DefaultAppState::new`, and `iris-android-app`'s cdylib calls it from //! `AndroidAppState::new`. Nothing here mentions either backend by name -- //! it only needs `Rsc: HasEvents` (for `.on(...)`) and `Rsc::State: //! FocusHost` (for `.attr::(())`), both of which every backend //! implements. use iris::prelude::*; use std::{cell::RefCell, rc::Rc}; pub struct ClientWidgets { pub info: WeakWidget, } pub fn build(rsc: &mut Rsc, ui_state: &mut impl HasRoot) -> ClientWidgets where Rsc::State: FocusHost, { let rrect = rect(Color::WHITE).radius(20); let pad_test = ( rrect.color(Color::BLUE), ( rrect .color(Color::RED) .sized((100, 100)) .center() .width(rest(2)), ( rrect.color(Color::ORANGE), rrect.color(Color::LIME).pad(10.0), ) .span(Dir::RIGHT) .width(rest(2)), rrect.color(Color::YELLOW), ) .span(Dir::RIGHT) .pad(10) .width(rest(3)), ) .span(Dir::RIGHT) .add(rsc); let span_test = ( rrect.color(Color::GREEN).width(100), rrect.color(Color::ORANGE), rrect.color(Color::CYAN), rrect.color(Color::BLUE).width(rel(0.5)), rrect.color(Color::MAGENTA).width(100), rrect.color(Color::RED).width(100), ) .span(Dir::LEFT) .add(rsc); let span_add = Span::empty(Dir::RIGHT).add(rsc); let add_button = rect(Color::LIME) .radius(30) .on(CursorSense::click(), move |_, rsc| { let child = image(include_bytes!("../assets/sungals.png")) .center() .add_strong(rsc); span_add(rsc).push(child); }) .sized((150, 150)) .align(Align::BOT_RIGHT); let del_button = rect(Color::RED) .radius(30) .on(CursorSense::click(), move |_, rsc| { span_add(rsc).pop(); }) .sized((150, 150)) .align(Align::BOT_LEFT); let span_add_test = (span_add, add_button, del_button).stack().add(rsc); let btext = |content| wtext(content).size(30); let text_test = ( btext("this is a").align(Align::LEFT), btext("teeeeeeeest").align(Align::RIGHT), btext("okkk\nokkkkkk!").align(Align::LEFT), btext("hmm"), btext("a"), ( btext("'").family(Family::Monospace).align(Align::TOP), btext("'").family(Family::Monospace), btext(":gamer mode").family(Family::Monospace), rect(Color::CYAN).sized((10, 10)).center(), rect(Color::RED).sized((100, 100)).center(), rect(Color::PURPLE).sized((50, 50)).align(Align::TOP), ) .span(Dir::RIGHT) .center(), wtext("pretty cool right?").size(50), ) .span(Dir::DOWN) .add(rsc); let texts = Span::empty(Dir::DOWN).gap(10).add(rsc); let msg_area = texts .scrollable(Axis::Y, Pin::Start) .masked() .background(rect(Color::SKY)); let add_text = wtext("add") .editable(EditMode::MultiLine) .text_align(Align::LEFT) .size(30) .attr::(()) .on(Submit, move |ctx, rsc| { let w = ctx.widget; let content = w.edit(rsc).take(); let text = wtext(content) .editable(EditMode::MultiLine) .size(30) .text_align(Align::LEFT) .wrap(true) .attr::(()); let msg_box = text .background(rect(Color::WHITE.darker(0.5))) .add_strong(rsc); texts(rsc).push(msg_box); }) .add(rsc); let text_edit_scroll = ( msg_area.height(rest(1)), ( Rect::new(Color::WHITE.darker(0.9)), ( add_text.width(rest(1)), Rect::new(Color::GREEN) .on(CursorSense::click(), move |ctx, rsc: &mut Rsc| { rsc.run_event::(add_text, (), ctx.state); }) .sized((40, 40)), ) .span(Dir::RIGHT) .pad(10), ) .stack() .size(StackSize::Child(1)) .layer_offset(1) .align(Align::BOT), ) .span(Dir::DOWN) .add(rsc); let main = WidgetPtr::new().add(rsc); let vals = Rc::new(RefCell::new((0, Vec::new()))); let mut switch_button = |color, to: WeakWidget, label| { let to = to.upgrade(rsc); let vec = &mut vals.borrow_mut().1; let i = vec.len(); if vec.is_empty() { vec.push(None); main(rsc).set(to); } else { vec.push(Some(to)); } let vals = vals.clone(); let rect = rect(color) .on(CursorSense::click(), move |ctx, rsc| { let (prev, vec) = &mut *vals.borrow_mut(); if let Some(h) = vec[i].take() { vec[*prev] = main(rsc).replace(h); *prev = i; } ctx.widget(rsc).color = color.darker(0.3); }) .on( CursorSense::HoverStart | CursorSense::unclick(), move |ctx, rsc| { ctx.widget(rsc).color = color.brighter(0.2); }, ) .on(CursorSense::HoverEnd, move |ctx, rsc| { ctx.widget(rsc).color = color; }) // I4 (RUST.md): the tabs screen's only named controls, and the // ones the emulator step at the bottom of that box taps by // name -- `ui-trace record --do "tap 'pad'"` and so on. `.label` // slots into this chain like any other widget combinator // (`RefFnTag` in `core/src/widget/tag.rs`); it does not have to // be the last thing before `.add`. .label(label); (rect, wtext(label).size(30).text_align(Align::CENTER)).stack() }; let tabs = ( switch_button(Color::RED, pad_test, "pad"), switch_button(Color::GREEN, span_test, "span"), switch_button(Color::BLUE, span_add_test, "image span"), switch_button(Color::MAGENTA, text_test, "text layout"), switch_button( Color::YELLOW.mul_rgb(0.5), text_edit_scroll, "text edit scroll", ), ) .span(Dir::RIGHT); let info = wtext("").add(rsc); let info_sect = info.pad(10).align(Align::RIGHT); ((tabs.height(40), main.pad(10)).span(Dir::DOWN), info_sect) .stack() .set_root(rsc, ui_state); ClientWidgets { info } }