use anyhow::Result;
use fission_core::env::Env;
use fission_core::internal::BuildCtx;
use fission_core::internal::InternalLoweringCx;
use fission_core::ui::{Grid, GridItem, TextInput, Widget};
use fission_core::Runtime;
use fission_core::{build, op::GridTrack, View, WidgetId, WidgetIdExt};
use fission_layout::{LayoutEngine, LineMetric, TextMeasurer};
use fission_render::{RenderScene, Renderer};
use fission_shell_desktop::Pipeline;
use fission_widgets::{HStack, MenuButton, MenuItem, VStack};
use std::sync::Arc;
#[derive(Debug, Default, Clone)]
struct GlobalState {
open: bool,
}
impl fission_core::action::GlobalState for GlobalState {}
struct MockRenderer;
impl Renderer for MockRenderer {
fn render_scene(&mut self, _scene: &RenderScene) -> Result<()> {
Ok(())
}
}
struct MockMeasurer;
impl TextMeasurer for MockMeasurer {
fn measure(&self, _text: &str, _font_size: f32, _available_width: Option<f32>) -> (f32, f32) {
(80.0, 20.0)
}
fn hit_test(
&self,
_text: &str,
_font_size: f32,
_available_width: Option<f32>,
_x: f32,
_y: f32,
) -> usize {
0
}
fn get_line_metrics(
&self,
_text: &str,
_font_size: f32,
_available_width: Option<f32>,
) -> Vec<LineMetric> {
vec![]
}
}
#[derive(Clone)]
struct Root;
impl From<Root> for Widget {
fn from(_component: Root) -> Self {
let (_ctx, view) = fission_core::build::current::<GlobalState>();
Grid {
columns: vec![
GridTrack::Points(220.0),
GridTrack::Points(380.0),
GridTrack::Fr(1.0),
],
rows: vec![GridTrack::Fr(1.0)],
children: vec![GridItem::new(VStack {
spacing: Some(0.0),
children: vec![HStack {
spacing: Some(8.0),
children: vec![
TextInput {
width: Some(200.0),
..Default::default()
}
.into(),
MenuButton {
id: WidgetId::explicit("test_menu"),
label: "Filter".into(),
is_open: view.state().open,
on_toggle: None,
items: vec![
MenuItem {
label: "All".into(),
icon: None,
on_select: None,
},
MenuItem {
label: "Unread".into(),
icon: None,
on_select: None,
},
],
}
.into(),
],
}
.into()],
})
.cell(1, 2)
.into()],
..Default::default()
}
.into()
}
}
#[test]
fn flyout_does_not_shift_content() -> Result<()> {
let env = Env::default();
let mut runtime = Runtime::default();
runtime.add_app_state(Box::new(GlobalState { open: false }))?;
let mut layout = LayoutEngine::new().with_measurer(Arc::new(MockMeasurer));
let mut pipe = Pipeline::new();
let (node_tree, _portals) = {
let state = runtime.get_app_state::<GlobalState>().unwrap();
let view = View::new(
state,
&runtime.runtime_state,
&env,
pipe.last_snapshot.as_ref(),
);
let mut ctx = BuildCtx::new();
let node: Widget = build::enter(&mut ctx, &view, || Root.into());
let portals_with_ids = ctx.take_portals();
let portals: Vec<Widget> = portals_with_ids
.into_iter()
.map(|(id, node)| {
if let Some(id) = id {
fission_core::ui::Container::new(node).id(id).into()
} else {
node
}
})
.collect();
(node, portals)
};
let mut cx = InternalLoweringCx::new(&env, &runtime.runtime_state, None, None);
let root_id = fission_core::internal::lower_widget(&node_tree, &mut cx);
cx.ir.root = Some(root_id);
let ir1 = cx.ir;
let viewport = fission_layout::LayoutSize {
width: 1024.0,
height: 768.0,
};
let _ = pipe.render(
ir1.clone(),
viewport,
&mut layout,
&runtime.runtime_state.scroll,
&mut MockRenderer,
&runtime.runtime_state.video,
&runtime.runtime_state.web,
&env,
)?;
let snap1 = pipe.last_snapshot.clone().expect("snapshot1");
let anchor_node = WidgetId::derived(WidgetId::explicit("test_menu").as_u128(), &[]);
let anchor_rect1 = snap1.get_node_rect(anchor_node).expect("anchor rect1");
{
if let Some(state) = runtime.get_app_state_mut::<GlobalState>() {
state.open = true;
}
let (node_tree, portals) = {
let state = runtime.get_app_state::<GlobalState>().unwrap();
let view = View::new(
state,
&runtime.runtime_state,
&env,
pipe.last_snapshot.as_ref(),
);
let mut ctx = BuildCtx::new();
let node: Widget = build::enter(&mut ctx, &view, || Root.into());
let portals_with_ids = ctx.take_portals();
let portals: Vec<Widget> = portals_with_ids
.into_iter()
.map(|(id, node)| {
if let Some(id) = id {
fission_core::ui::Container::new(node).id(id).into()
} else {
node
}
})
.collect();
(node, portals)
};
let final_root = if portals.is_empty() {
node_tree
} else {
use fission_core::ui::{Overlay, ZStack};
Overlay {
id: None,
content: node_tree,
overlay: ZStack {
children: portals,
..Default::default()
}
.into(),
}
.into()
};
let mut cx = InternalLoweringCx::new(
&env,
&runtime.runtime_state,
None,
pipe.last_snapshot.as_ref(),
);
let root_id = fission_core::internal::lower_widget(&final_root, &mut cx);
cx.ir.root = Some(root_id);
let ir2 = cx.ir;
let _ = pipe.render(
ir2.clone(),
viewport,
&mut layout,
&runtime.runtime_state.scroll,
&mut MockRenderer,
&runtime.runtime_state.video,
&runtime.runtime_state.web,
&env,
)?;
let snap2 = pipe.last_snapshot.clone().expect("snapshot2");
let anchor_rect2 = snap2.get_node_rect(anchor_node).expect("anchor rect2");
assert_eq!(
anchor_rect1, anchor_rect2,
"anchor shifted when flyout opened!"
);
}
Ok(())
}