use instant::Duration;
use std::collections::HashMap;
use std::fmt::Debug;
use std::hash::Hash;
use std::marker::PhantomData;
use std::rc::Rc;
use tracing::{instrument, trace};
use crate::commands::SCROLL_TO_VIEW;
use crate::kurbo::{Circle, Line};
use crate::widget::prelude::*;
use crate::widget::{Axis, Flex, Label, LabelText, LensScopeTransfer, Painter, Scope, ScopePolicy};
use crate::{theme, Affine, Data, Insets, Lens, Point, SingleUse, WidgetExt, WidgetPod};
type TabsScope<TP> = Scope<TabsScopePolicy<TP>, Box<dyn Widget<TabsState<TP>>>>;
type TabBodyPod<TP> = WidgetPod<<TP as TabsPolicy>::Input, <TP as TabsPolicy>::BodyWidget>;
type TabBarPod<TP> = WidgetPod<TabsState<TP>, Box<dyn Widget<TabsState<TP>>>>;
type TabIndex = usize;
type Nanos = u64;
pub struct TabInfo<Input> {
pub name: LabelText<Input>,
pub can_close: bool,
}
impl<Input> TabInfo<Input> {
pub fn new(name: impl Into<LabelText<Input>>, can_close: bool) -> Self {
TabInfo {
name: name.into(),
can_close,
}
}
}
pub trait TabsPolicy: Data {
type Key: Hash + Eq + Clone;
type Input: Data;
type BodyWidget: Widget<Self::Input>;
type LabelWidget: Widget<Self::Input>;
type Build;
fn tabs_changed(&self, old_data: &Self::Input, data: &Self::Input) -> bool;
fn tabs(&self, data: &Self::Input) -> Vec<Self::Key>;
fn tab_info(&self, key: Self::Key, data: &Self::Input) -> TabInfo<Self::Input>;
fn tab_body(&self, key: Self::Key, data: &Self::Input) -> Self::BodyWidget;
fn tab_label(
&self,
key: Self::Key,
info: TabInfo<Self::Input>,
data: &Self::Input,
) -> Self::LabelWidget;
#[allow(unused_variables)]
fn close_tab(&self, key: Self::Key, data: &mut Self::Input) {}
#[allow(unused_variables)]
fn build(build: Self::Build) -> Self {
panic!("TabsPolicy::Build called on a policy that does not support incremental building")
}
fn default_make_label(info: TabInfo<Self::Input>) -> Label<Self::Input> {
Label::new(info.name).with_text_color(theme::FOREGROUND_LIGHT)
}
}
#[derive(Clone)]
pub struct StaticTabs<T> {
tabs: Rc<[InitialTab<T>]>,
}
impl<T> Default for StaticTabs<T> {
fn default() -> Self {
StaticTabs { tabs: Rc::new([]) }
}
}
impl<T: Data> Data for StaticTabs<T> {
fn same(&self, _other: &Self) -> bool {
true
}
}
impl<T: Data> TabsPolicy for StaticTabs<T> {
type Key = usize;
type Input = T;
type BodyWidget = Box<dyn Widget<T>>;
type LabelWidget = Label<T>;
type Build = Vec<InitialTab<T>>;
fn tabs_changed(&self, _old_data: &T, _data: &T) -> bool {
false
}
fn tabs(&self, _data: &T) -> Vec<Self::Key> {
(0..self.tabs.len()).collect()
}
fn tab_info(&self, key: Self::Key, _data: &T) -> TabInfo<Self::Input> {
TabInfo::new(
self.tabs[key]
.name
.take()
.expect("StaticTabs LabelText can only be retrieved once"),
false,
)
}
fn tab_body(&self, key: Self::Key, _data: &T) -> Self::BodyWidget {
self.tabs
.get(key)
.and_then(|initial_tab| initial_tab.child.take())
.expect("StaticTabs body widget can only be retrieved once")
}
fn tab_label(
&self,
_key: Self::Key,
info: TabInfo<Self::Input>,
_data: &Self::Input,
) -> Self::LabelWidget {
Self::default_make_label(info)
}
fn build(build: Self::Build) -> Self {
StaticTabs { tabs: build.into() }
}
}
pub trait AddTab: TabsPolicy {
fn add_tab(
build: &mut Self::Build,
name: impl Into<LabelText<Self::Input>>,
child: impl Widget<Self::Input> + 'static,
);
}
impl<T: Data> AddTab for StaticTabs<T> {
fn add_tab(
build: &mut Self::Build,
name: impl Into<LabelText<T>>,
child: impl Widget<T> + 'static,
) {
build.push(InitialTab::new(name, child))
}
}
#[derive(Clone, Lens, Data)]
pub struct TabsState<TP: TabsPolicy> {
inner: TP::Input,
selected: TabIndex,
policy: TP,
}
impl<TP: TabsPolicy> TabsState<TP> {
pub fn new(inner: TP::Input, selected: usize, policy: TP) -> Self {
TabsState {
inner,
selected,
policy,
}
}
}
struct TabBar<TP: TabsPolicy> {
axis: Axis,
edge: TabsEdge,
tabs: Vec<(TP::Key, TabBarPod<TP>)>,
hot: Option<TabIndex>,
phantom_tp: PhantomData<TP>,
}
impl<TP: TabsPolicy> TabBar<TP> {
fn new(axis: Axis, edge: TabsEdge) -> Self {
TabBar {
axis,
edge,
tabs: vec![],
hot: None,
phantom_tp: Default::default(),
}
}
fn find_idx(&self, pos: Point) -> Option<TabIndex> {
let major_pix = self.axis.major_pos(pos);
let axis = self.axis;
let res = self
.tabs
.binary_search_by_key(&((major_pix * 10.) as i64), |(_, tab)| {
let rect = tab.layout_rect();
let far_pix = axis.major_pos(rect.origin()) + axis.major(rect.size());
(far_pix * 10.) as i64
});
match res {
Ok(idx) => Some(idx),
Err(idx) if idx < self.tabs.len() => Some(idx),
_ => None,
}
}
fn ensure_tabs(&mut self, data: &TabsState<TP>) {
ensure_for_tabs(&mut self.tabs, &data.policy, &data.inner, |policy, key| {
let info = policy.tab_info(key.clone(), &data.inner);
let can_close = info.can_close;
let label = data
.policy
.tab_label(key.clone(), info, &data.inner)
.lens(TabsState::<TP>::inner)
.padding(Insets::uniform_xy(9., 5.));
if can_close {
let close_button = Painter::new(|ctx, _, env| {
let circ_bounds = ctx.size().to_rect().inset(-2.);
let cross_bounds = circ_bounds.inset(-5.);
if ctx.is_hot() {
ctx.render_ctx.fill(
Circle::new(
circ_bounds.center(),
f64::min(circ_bounds.height(), circ_bounds.width()) / 2.,
),
&env.get(theme::BORDER_LIGHT),
);
}
let cross_color = &env.get(if ctx.is_hot() {
theme::BACKGROUND_DARK
} else {
theme::BORDER_LIGHT
});
ctx.render_ctx.stroke(
Line::new(
(cross_bounds.x0, cross_bounds.y0),
(cross_bounds.x1, cross_bounds.y1),
),
cross_color,
2.,
);
ctx.render_ctx.stroke(
Line::new(
(cross_bounds.x1, cross_bounds.y0),
(cross_bounds.x0, cross_bounds.y1),
),
cross_color,
2.,
);
})
.fix_size(20., 20.);
let row = Flex::row()
.with_child(label)
.with_child(close_button.on_click(
move |_ctx, data: &mut TabsState<TP>, _env| {
data.policy.close_tab(key.clone(), &mut data.inner);
},
));
WidgetPod::new(Box::new(row))
} else {
WidgetPod::new(Box::new(label))
}
});
}
}
impl<TP: TabsPolicy> Widget<TabsState<TP>> for TabBar<TP> {
#[instrument(name = "TabBar", level = "trace", skip(self, ctx, event, data, env))]
fn event(&mut self, ctx: &mut EventCtx, event: &Event, data: &mut TabsState<TP>, env: &Env) {
match event {
Event::MouseDown(e) => {
if let Some(idx) = self.find_idx(e.pos) {
data.selected = idx;
}
}
Event::MouseMove(e) => {
let new_hot = if ctx.is_hot() {
self.find_idx(e.pos)
} else {
None
};
if new_hot != self.hot {
self.hot = new_hot;
ctx.request_paint();
}
}
_ => {}
}
for (_, tab) in self.tabs.iter_mut() {
tab.event(ctx, event, data, env);
}
}
#[instrument(name = "TabBar", level = "trace", skip(self, ctx, event, data, env))]
fn lifecycle(
&mut self,
ctx: &mut LifeCycleCtx,
event: &LifeCycle,
data: &TabsState<TP>,
env: &Env,
) {
if let LifeCycle::WidgetAdded = event {
self.ensure_tabs(data);
ctx.children_changed();
}
for (_, tab) in self.tabs.iter_mut() {
tab.lifecycle(ctx, event, data, env);
}
}
#[instrument(name = "TabBar", level = "trace", skip(self, ctx, old_data, data, env))]
fn update(
&mut self,
ctx: &mut UpdateCtx,
old_data: &TabsState<TP>,
data: &TabsState<TP>,
env: &Env,
) {
for (_, tab) in self.tabs.iter_mut() {
tab.update(ctx, data, env)
}
if data.policy.tabs_changed(&old_data.inner, &data.inner) {
self.ensure_tabs(data);
ctx.children_changed();
} else if old_data.selected != data.selected {
ctx.request_paint();
}
}
#[instrument(name = "TabBar", level = "trace", skip(self, ctx, bc, data, env))]
fn layout(
&mut self,
ctx: &mut LayoutCtx,
bc: &BoxConstraints,
data: &TabsState<TP>,
env: &Env,
) -> Size {
let mut major: f64 = 0.;
let mut minor: f64 = 0.;
for (_, tab) in self.tabs.iter_mut() {
let size = tab.layout(ctx, bc, data, env);
tab.set_origin(ctx, self.axis.pack(major, 0.).into());
major += self.axis.major(size);
minor = minor.max(self.axis.minor(size));
}
let wanted = self.axis.pack(major.max(self.axis.major(bc.max())), minor);
let size = bc.constrain(wanted);
trace!("Computed size: {}", size);
size
}
#[instrument(name = "TabBar", level = "trace", skip(self, ctx, data, env))]
fn paint(&mut self, ctx: &mut PaintCtx, data: &TabsState<TP>, env: &Env) {
let hl_thickness = 2.;
let highlight = env.get(theme::PRIMARY_LIGHT);
for (idx, (_, tab)) in self.tabs.iter_mut().enumerate() {
let layout_rect = tab.layout_rect();
let expanded_size = self.axis.pack(
self.axis.major(layout_rect.size()),
self.axis.minor(ctx.size()),
);
let rect = layout_rect.with_size(expanded_size);
let bg = match (idx == data.selected, Some(idx) == self.hot) {
(_, true) => env.get(theme::BUTTON_DARK),
(true, false) => env.get(theme::BACKGROUND_LIGHT),
_ => env.get(theme::BACKGROUND_DARK),
};
ctx.fill(rect, &bg);
tab.paint(ctx, data, env);
if idx == data.selected {
let (maj_near, maj_far) = self.axis.major_span(rect);
let (min_near, min_far) = self.axis.minor_span(rect);
let minor_pos = if let TabsEdge::Trailing = self.edge {
min_near + (hl_thickness / 2.)
} else {
min_far - (hl_thickness / 2.)
};
ctx.stroke(
Line::new(
self.axis.pack(maj_near, minor_pos),
self.axis.pack(maj_far, minor_pos),
),
&highlight,
hl_thickness,
)
}
}
}
}
struct TabsTransitionState {
previous_idx: TabIndex,
current_time: u64,
duration: Nanos,
increasing: bool,
}
impl TabsTransitionState {
fn new(previous_idx: TabIndex, duration: Nanos, increasing: bool) -> Self {
TabsTransitionState {
previous_idx,
current_time: 0,
duration,
increasing,
}
}
fn live(&self) -> bool {
self.current_time < self.duration
}
fn fraction(&self) -> f64 {
(self.current_time as f64) / (self.duration as f64)
}
fn previous_transform(&self, axis: Axis, main: f64) -> Affine {
let x = if self.increasing {
-main * self.fraction()
} else {
main * self.fraction()
};
Affine::translate(axis.pack(x, 0.))
}
fn selected_transform(&self, axis: Axis, main: f64) -> Affine {
let x = if self.increasing {
main * (1.0 - self.fraction())
} else {
-main * (1.0 - self.fraction())
};
Affine::translate(axis.pack(x, 0.))
}
}
fn ensure_for_tabs<Content, TP: TabsPolicy + ?Sized>(
contents: &mut Vec<(TP::Key, Content)>,
policy: &TP,
data: &TP::Input,
f: impl Fn(&TP, TP::Key) -> Content,
) -> Vec<usize> {
let mut existing_by_key: HashMap<TP::Key, Content> = contents.drain(..).collect();
let mut existing_idx = Vec::new();
for key in policy.tabs(data).into_iter() {
let next = if let Some(child) = existing_by_key.remove(&key) {
existing_idx.push(contents.len());
child
} else {
f(policy, key.clone())
};
contents.push((key.clone(), next))
}
existing_idx
}
struct TabsBody<TP: TabsPolicy> {
children: Vec<(TP::Key, TabBodyPod<TP>)>,
axis: Axis,
transition: TabsTransition,
transition_state: Option<TabsTransitionState>,
phantom_tp: PhantomData<TP>,
}
impl<TP: TabsPolicy> TabsBody<TP> {
fn new(axis: Axis, transition: TabsTransition) -> TabsBody<TP> {
TabsBody {
children: vec![],
axis,
transition,
transition_state: None,
phantom_tp: Default::default(),
}
}
fn make_tabs(&mut self, data: &TabsState<TP>) -> Vec<usize> {
ensure_for_tabs(
&mut self.children,
&data.policy,
&data.inner,
|policy, key| WidgetPod::new(policy.tab_body(key, &data.inner)),
)
}
fn active_child(&mut self, state: &TabsState<TP>) -> Option<&mut TabBodyPod<TP>> {
Self::child(&mut self.children, state.selected)
}
fn child(
children: &mut [(TP::Key, TabBodyPod<TP>)],
idx: usize,
) -> Option<&mut TabBodyPod<TP>> {
children.get_mut(idx).map(|x| &mut x.1)
}
fn child_pods(&mut self) -> impl Iterator<Item = &mut TabBodyPod<TP>> {
self.children.iter_mut().map(|x| &mut x.1)
}
}
impl<TP: TabsPolicy> Widget<TabsState<TP>> for TabsBody<TP> {
#[instrument(name = "TabsBody", level = "trace", skip(self, ctx, event, data, env))]
fn event(&mut self, ctx: &mut EventCtx, event: &Event, data: &mut TabsState<TP>, env: &Env) {
if let Event::Notification(notification) = event {
if notification.is(SCROLL_TO_VIEW)
&& Some(notification.route()) != self.active_child(data).map(|w| w.id())
{
ctx.set_handled();
}
} else if event.should_propagate_to_hidden() {
for child in self.child_pods() {
child.event(ctx, event, &mut data.inner, env);
}
} else if let Some(child) = self.active_child(data) {
child.event(ctx, event, &mut data.inner, env);
}
if let (Some(t_state), Event::AnimFrame(interval)) = (&mut self.transition_state, event) {
let interval = if t_state.current_time == 0 {
1
} else {
*interval
};
t_state.current_time += interval;
if t_state.live() {
ctx.request_anim_frame();
} else {
self.transition_state = None;
}
ctx.request_paint();
}
}
#[instrument(name = "TabsBody", level = "trace", skip(self, ctx, event, data, env))]
fn lifecycle(
&mut self,
ctx: &mut LifeCycleCtx,
event: &LifeCycle,
data: &TabsState<TP>,
env: &Env,
) {
if let LifeCycle::WidgetAdded = event {
self.make_tabs(data);
ctx.children_changed();
}
if event.should_propagate_to_hidden() {
for child in self.child_pods() {
child.lifecycle(ctx, event, &data.inner, env);
}
} else if let Some(child) = self.active_child(data) {
child.lifecycle(ctx, event, &data.inner, env);
}
}
#[instrument(
name = "TabsBody",
level = "trace",
skip(self, ctx, old_data, data, env)
)]
fn update(
&mut self,
ctx: &mut UpdateCtx,
old_data: &TabsState<TP>,
data: &TabsState<TP>,
env: &Env,
) {
let init = if data.policy.tabs_changed(&old_data.inner, &data.inner) {
ctx.children_changed();
Some(self.make_tabs(data))
} else {
None
};
if old_data.selected != data.selected {
self.transition_state = self
.transition
.tab_changed(old_data.selected, data.selected);
ctx.children_changed();
if self.transition_state.is_some() {
ctx.request_anim_frame();
}
}
if let Some(init) = init {
for idx in init {
if let Some(child) = Self::child(&mut self.children, idx) {
child.update(ctx, &data.inner, env)
}
}
} else {
for child in self.child_pods() {
child.update(ctx, &data.inner, env);
}
}
}
#[instrument(name = "TabsBody", level = "trace", skip(self, ctx, bc, data, env))]
fn layout(
&mut self,
ctx: &mut LayoutCtx,
bc: &BoxConstraints,
data: &TabsState<TP>,
env: &Env,
) -> Size {
let inner = &data.inner;
for child in self.child_pods() {
child.layout(ctx, bc, inner, env);
child.set_origin(ctx, Point::ORIGIN);
}
bc.max()
}
#[instrument(name = "TabsBody", level = "trace", skip(self, ctx, data, env))]
fn paint(&mut self, ctx: &mut PaintCtx, data: &TabsState<TP>, env: &Env) {
if let Some(trans) = &self.transition_state {
let axis = self.axis;
let size = ctx.size();
let major = axis.major(size);
ctx.clip(size.to_rect());
let children = &mut self.children;
if let Some(ref mut prev) = Self::child(children, trans.previous_idx) {
ctx.with_save(|ctx| {
ctx.transform(trans.previous_transform(axis, major));
prev.paint_raw(ctx, &data.inner, env);
})
}
if let Some(ref mut child) = Self::child(children, data.selected) {
ctx.with_save(|ctx| {
ctx.transform(trans.selected_transform(axis, major));
child.paint_raw(ctx, &data.inner, env);
})
}
} else if let Some(ref mut child) = Self::child(&mut self.children, data.selected) {
child.paint_raw(ctx, &data.inner, env);
}
}
}
struct TabsScopePolicy<TP> {
tabs_from_data: TP,
selected: TabIndex,
}
impl<TP> TabsScopePolicy<TP> {
fn new(tabs_from_data: TP, selected: TabIndex) -> Self {
Self {
tabs_from_data,
selected,
}
}
}
impl<TP: TabsPolicy> ScopePolicy for TabsScopePolicy<TP> {
type In = TP::Input;
type State = TabsState<TP>;
type Transfer = LensScopeTransfer<tabs_state_derived_lenses::inner<TP>, Self::In, Self::State>;
fn create(self, inner: &Self::In) -> (Self::State, Self::Transfer) {
(
TabsState::new(inner.clone(), self.selected, self.tabs_from_data),
LensScopeTransfer::new(Self::State::inner),
)
}
}
#[derive(Data, Copy, Clone, Debug, PartialOrd, PartialEq, Eq)]
pub enum TabsTransition {
Instant,
Slide(Nanos),
}
impl Default for TabsTransition {
fn default() -> Self {
TabsTransition::Slide(Duration::from_millis(250).as_nanos() as Nanos)
}
}
impl TabsTransition {
fn tab_changed(self, old: TabIndex, new: TabIndex) -> Option<TabsTransitionState> {
match self {
TabsTransition::Instant => None,
TabsTransition::Slide(dur) => Some(TabsTransitionState::new(old, dur, old < new)),
}
}
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, Data)]
pub enum TabsEdge {
Leading,
Trailing,
}
impl Default for TabsEdge {
fn default() -> Self {
Self::Leading
}
}
pub struct InitialTab<T> {
name: SingleUse<LabelText<T>>, child: SingleUse<Box<dyn Widget<T>>>, }
impl<T: Data> InitialTab<T> {
fn new(name: impl Into<LabelText<T>>, child: impl Widget<T> + 'static) -> Self {
InitialTab {
name: SingleUse::new(name.into()),
child: SingleUse::new(child.boxed()),
}
}
}
#[allow(clippy::large_enum_variant)]
enum TabsContent<TP: TabsPolicy> {
Building {
tabs: TP::Build,
index: TabIndex,
},
Complete {
tabs: TP,
index: TabIndex,
},
Running {
scope: WidgetPod<TP::Input, TabsScope<TP>>,
},
Swapping,
}
pub struct Tabs<TP: TabsPolicy> {
axis: Axis,
edge: TabsEdge,
transition: TabsTransition,
content: TabsContent<TP>,
}
impl<T: Data> Tabs<StaticTabs<T>> {
pub fn new() -> Self {
Tabs::building(Vec::new())
}
}
impl<T: Data> Default for Tabs<StaticTabs<T>> {
fn default() -> Self {
Self::new()
}
}
impl<TP: TabsPolicy> Tabs<TP> {
fn of_content(content: TabsContent<TP>) -> Self {
Tabs {
axis: Axis::Horizontal,
edge: Default::default(),
transition: Default::default(),
content,
}
}
pub fn for_policy(tabs: TP) -> Self {
Self::of_content(TabsContent::Complete { tabs, index: 0 })
}
fn building(tabs_from_data: TP::Build) -> Self
where
TP: AddTab,
{
Self::of_content(TabsContent::Building {
tabs: tabs_from_data,
index: 0,
})
}
pub fn with_axis(mut self, axis: Axis) -> Self {
self.axis = axis;
self
}
pub fn with_edge(mut self, edge: TabsEdge) -> Self {
self.edge = edge;
self
}
pub fn with_transition(mut self, transition: TabsTransition) -> Self {
self.transition = transition;
self
}
pub fn with_tab(
mut self,
name: impl Into<LabelText<TP::Input>>,
child: impl Widget<TP::Input> + 'static,
) -> Tabs<TP>
where
TP: AddTab,
{
self.add_tab(name, child);
self
}
pub fn with_tab_index(mut self, idx: TabIndex) -> Self {
self.set_tab_index(idx);
self
}
pub fn add_tab(
&mut self,
name: impl Into<LabelText<TP::Input>>,
child: impl Widget<TP::Input> + 'static,
) where
TP: AddTab,
{
if let TabsContent::Building { tabs, .. } = &mut self.content {
TP::add_tab(tabs, name, child)
} else {
tracing::warn!("Can't add static tabs to a running or complete tabs instance!")
}
}
pub fn tab_index(&self) -> TabIndex {
let index = match &self.content {
TabsContent::Running { scope, .. } => scope.widget().state().map(|s| s.selected),
TabsContent::Building { index, .. } | TabsContent::Complete { index, .. } => {
Some(*index)
}
TabsContent::Swapping => None,
};
index.unwrap_or(0)
}
pub fn set_tab_index(&mut self, idx: TabIndex) {
match &mut self.content {
TabsContent::Running { scope, .. } => {
if let Some(state) = scope.widget_mut().state_mut() {
state.selected = idx
}
}
TabsContent::Building { index, .. } | TabsContent::Complete { index, .. } => {
*index = idx;
}
TabsContent::Swapping => (),
}
}
fn make_scope(&self, tabs_from_data: TP, idx: TabIndex) -> WidgetPod<TP::Input, TabsScope<TP>> {
let tabs_bar = TabBar::new(self.axis, self.edge);
let tabs_body = TabsBody::new(self.axis, self.transition)
.padding(5.)
.border(theme::BORDER_DARK, 0.5);
let mut layout: Flex<TabsState<TP>> = Flex::for_axis(self.axis.cross());
if let TabsEdge::Trailing = self.edge {
layout.add_flex_child(tabs_body, 1.);
layout.add_child(tabs_bar);
} else {
layout.add_child(tabs_bar);
layout.add_flex_child(tabs_body, 1.);
};
WidgetPod::new(Scope::new(
TabsScopePolicy::new(tabs_from_data, idx),
Box::new(layout),
))
}
}
impl<TP: TabsPolicy> Widget<TP::Input> for Tabs<TP> {
#[instrument(name = "Tabs", level = "trace", skip(self, ctx, event, data, env))]
fn event(&mut self, ctx: &mut EventCtx, event: &Event, data: &mut TP::Input, env: &Env) {
if let TabsContent::Running { scope } = &mut self.content {
scope.event(ctx, event, data, env);
}
}
#[instrument(name = "Tabs", level = "trace", skip(self, ctx, event, data, env))]
fn lifecycle(
&mut self,
ctx: &mut LifeCycleCtx,
event: &LifeCycle,
data: &TP::Input,
env: &Env,
) {
if let LifeCycle::WidgetAdded = event {
let content = std::mem::replace(&mut self.content, TabsContent::Swapping);
self.content = match content {
TabsContent::Building { tabs, index } => {
ctx.children_changed();
TabsContent::Running {
scope: self.make_scope(TP::build(tabs), index),
}
}
TabsContent::Complete { tabs, index } => {
ctx.children_changed();
TabsContent::Running {
scope: self.make_scope(tabs, index),
}
}
_ => content,
};
}
if let TabsContent::Running { scope } = &mut self.content {
scope.lifecycle(ctx, event, data, env)
}
}
#[instrument(name = "Tabs", level = "trace", skip(self, ctx, _old_data, data, env))]
fn update(&mut self, ctx: &mut UpdateCtx, _old_data: &TP::Input, data: &TP::Input, env: &Env) {
if let TabsContent::Running { scope } = &mut self.content {
scope.update(ctx, data, env);
}
}
#[instrument(name = "Tabs", level = "trace", skip(self, ctx, bc, data, env))]
fn layout(
&mut self,
ctx: &mut LayoutCtx,
bc: &BoxConstraints,
data: &TP::Input,
env: &Env,
) -> Size {
if let TabsContent::Running { scope } = &mut self.content {
let size = scope.layout(ctx, bc, data, env);
scope.set_origin(ctx, Point::ORIGIN);
size
} else {
bc.min()
}
}
#[instrument(name = "Tabs", level = "trace", skip(self, ctx, data, env))]
fn paint(&mut self, ctx: &mut PaintCtx, data: &TP::Input, env: &Env) {
if let TabsContent::Running { scope } = &mut self.content {
scope.paint(ctx, data, env)
}
}
}