Keep process contribution editors fully visible in compact windows
This commit is contained in:
@@ -0,0 +1,138 @@
|
||||
use super::*;
|
||||
use iced::advanced::{Layout, layout, widget};
|
||||
use iced::{Rectangle, Vector};
|
||||
|
||||
#[derive(Default)]
|
||||
struct Geometry {
|
||||
scrolls: Vec<(Option<widget::Id>, Rectangle, Vector)>,
|
||||
actions: Option<Rectangle>,
|
||||
}
|
||||
impl widget::Operation for Geometry {
|
||||
fn traverse(&mut self, operate: &mut dyn FnMut(&mut dyn widget::Operation)) {
|
||||
operate(self);
|
||||
}
|
||||
fn container(&mut self, id: Option<&widget::Id>, bounds: Rectangle) {
|
||||
if id == Some(&widget::Id::new("test-process-actions")) {
|
||||
self.actions = Some(bounds);
|
||||
}
|
||||
}
|
||||
fn scrollable(
|
||||
&mut self,
|
||||
id: Option<&widget::Id>,
|
||||
bounds: Rectangle,
|
||||
_: Rectangle,
|
||||
translation: Vector,
|
||||
_: &mut dyn widget::operation::Scrollable,
|
||||
) {
|
||||
self.scrolls.push((id.cloned(), bounds, translation));
|
||||
}
|
||||
}
|
||||
fn workspace(pending: bool) -> iced::Element<'static, Message, iced::Theme, ()> {
|
||||
let diagram = scrollable(iced::widget::Space::new().width(500).height(1200))
|
||||
.id("test-process-page")
|
||||
.height(Fill)
|
||||
.into();
|
||||
let form = if pending {
|
||||
editor_panel(
|
||||
container(iced::widget::Space::new().height(32))
|
||||
.id("test-process-actions")
|
||||
.into(),
|
||||
iced::widget::Space::new().height(1400).into(),
|
||||
)
|
||||
} else {
|
||||
iced::widget::Space::new().height(0).into()
|
||||
};
|
||||
panes(diagram, form, pending)
|
||||
}
|
||||
fn geometry(
|
||||
view: &mut iced::Element<'_, Message, iced::Theme, ()>,
|
||||
tree: &mut widget::Tree,
|
||||
size: Size,
|
||||
) -> (layout::Node, Geometry) {
|
||||
let node = view
|
||||
.as_widget_mut()
|
||||
.layout(tree, &(), &layout::Limits::new(Size::ZERO, size));
|
||||
let mut geometry = Geometry::default();
|
||||
view.as_widget_mut()
|
||||
.operate(tree, Layout::new(&node), &(), &mut geometry);
|
||||
(node, geometry)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn direct_process_pending_editor_uses_available_height_and_keeps_actions_visible() {
|
||||
// Actual available center-pane sizes at the minimum/default supported windows.
|
||||
// Space represents arbitrarily long diagram/form content; the production
|
||||
// pane/card builders and real Iced layout/scroll operations are exercised.
|
||||
for size in [Size::new(500., 570.), Size::new(840., 770.)] {
|
||||
let mut view = workspace(false);
|
||||
let mut tree = widget::Tree::new(view.as_widget());
|
||||
let (node, _) = geometry(&mut view, &mut tree, size);
|
||||
let mut scroll = widget::operation::scrollable::scroll_to::<()>(
|
||||
widget::Id::new("test-process-page"),
|
||||
widget::operation::scrollable::AbsoluteOffset {
|
||||
x: None,
|
||||
y: Some(180.),
|
||||
},
|
||||
);
|
||||
view.as_widget_mut()
|
||||
.operate(&mut tree, Layout::new(&node), &(), &mut scroll);
|
||||
view = workspace(true);
|
||||
tree.diff(view.as_widget());
|
||||
let (node, before) = geometry(&mut view, &mut tree, size);
|
||||
let page = before
|
||||
.scrolls
|
||||
.iter()
|
||||
.find(|s| s.0 == Some(widget::Id::new("test-process-page")))
|
||||
.unwrap();
|
||||
let form = before
|
||||
.scrolls
|
||||
.iter()
|
||||
.find(|s| s.0 == Some(widget::Id::new("process-form")))
|
||||
.unwrap();
|
||||
assert_eq!(node.size(), size);
|
||||
assert!(
|
||||
form.1.height >= 200.,
|
||||
"usable form viewport: {size:?} {:?}",
|
||||
form.1
|
||||
);
|
||||
assert!(form.1.y >= page.1.y + page.1.height);
|
||||
assert!(form.1.y + form.1.height <= size.height);
|
||||
assert_eq!(page.2.y, 180., "opening a form retains the page scroll");
|
||||
let actions = before.actions.unwrap();
|
||||
assert!(actions.y >= page.1.y + page.1.height);
|
||||
assert!(actions.y + actions.height <= form.1.y);
|
||||
let mut scroll = widget::operation::scrollable::scroll_to::<()>(
|
||||
widget::Id::new("process-form"),
|
||||
widget::operation::scrollable::AbsoluteOffset {
|
||||
x: None,
|
||||
y: Some(600.),
|
||||
},
|
||||
);
|
||||
view.as_widget_mut()
|
||||
.operate(&mut tree, Layout::new(&node), &(), &mut scroll);
|
||||
let (_, after) = geometry(&mut view, &mut tree, size);
|
||||
assert_eq!(
|
||||
after.actions,
|
||||
Some(actions),
|
||||
"Create/Cancel stay pinned during form scrolling"
|
||||
);
|
||||
assert_eq!(
|
||||
after.scrolls[0].2.y, 180.,
|
||||
"form scrolling does not move the diagram page"
|
||||
);
|
||||
assert!(
|
||||
after
|
||||
.scrolls
|
||||
.iter()
|
||||
.any(|s| s.0 == Some(widget::Id::new("process-form")) && s.2.y == 600.)
|
||||
);
|
||||
view = workspace(false);
|
||||
tree.diff(view.as_widget());
|
||||
let (_, closed) = geometry(&mut view, &mut tree, size);
|
||||
assert_eq!(
|
||||
closed.scrolls[0].2.y, 180.,
|
||||
"cancel retains the page scroll"
|
||||
);
|
||||
assert_eq!(closed.scrolls[0].1.height, size.height);
|
||||
}
|
||||
}
|
||||
@@ -305,6 +305,7 @@ pub(super) fn view<'a>(
|
||||
button("Back to caller").on_press(Message::TopologyCanvas(CanvasEvent::Back(context))),
|
||||
);
|
||||
}
|
||||
let mut form_panel: Element<'_, Message> = iced::widget::Space::new().height(0).into();
|
||||
if let Some(d) = state.draft.as_ref().filter(|d| d.owner == owner) {
|
||||
let mut form = column![text(d.tool.title()).size(16)].spacing(8);
|
||||
if let Some(link) = d.insertion.link.and_then(|id| p.relations.get(&id)) {
|
||||
@@ -473,21 +474,65 @@ pub(super) fn view<'a>(
|
||||
button("Cancel").on_press(event(Event::Cancel))
|
||||
]
|
||||
.spacing(7);
|
||||
out = out.push(
|
||||
container(
|
||||
column![
|
||||
actions,
|
||||
scrollable(form).height((app.window_size.height - 520.).clamp(150., 260.))
|
||||
]
|
||||
.spacing(8),
|
||||
)
|
||||
.padding(12)
|
||||
.style(style::card),
|
||||
);
|
||||
form_panel = editor_panel(actions.into(), form.into());
|
||||
}
|
||||
super::super::scenario_scroll::observe_topology(
|
||||
context,
|
||||
scenario::ScrollArea::Page,
|
||||
scrollable(out.padding(18)),
|
||||
panes(
|
||||
super::super::scenario_scroll::observe_topology(
|
||||
context,
|
||||
scenario::ScrollArea::Page,
|
||||
scrollable(out.padding(18)),
|
||||
),
|
||||
form_panel,
|
||||
pending,
|
||||
)
|
||||
}
|
||||
|
||||
/// Keep the diagram mounted while an independent editor uses the remaining
|
||||
/// workspace height. The editor must never be clipped by the page scrollable.
|
||||
fn panes<'a, R: iced::advanced::Renderer + 'a>(
|
||||
diagram: iced::Element<'a, Message, iced::Theme, R>,
|
||||
form: iced::Element<'a, Message, iced::Theme, R>,
|
||||
pending: bool,
|
||||
) -> iced::Element<'a, Message, iced::Theme, R> {
|
||||
column![
|
||||
container(diagram).width(Fill).height(if pending {
|
||||
iced::Length::FillPortion(1)
|
||||
} else {
|
||||
Fill
|
||||
}),
|
||||
container(form).width(Fill).height(if pending {
|
||||
iced::Length::FillPortion(1)
|
||||
} else {
|
||||
iced::Length::Shrink
|
||||
}),
|
||||
]
|
||||
.width(Fill)
|
||||
.height(Fill)
|
||||
.into()
|
||||
}
|
||||
|
||||
fn editor_panel<'a, R: iced::advanced::text::Renderer + 'a>(
|
||||
actions: iced::Element<'a, Message, iced::Theme, R>,
|
||||
fields: iced::Element<'a, Message, iced::Theme, R>,
|
||||
) -> iced::Element<'a, Message, iced::Theme, R> {
|
||||
container(
|
||||
container(column![actions, scrollable(fields).id("process-form").height(Fill)].spacing(8))
|
||||
.width(Fill)
|
||||
.height(Fill)
|
||||
.padding(12)
|
||||
.style(style::card),
|
||||
)
|
||||
.width(Fill)
|
||||
.height(Fill)
|
||||
.padding(iced::Padding {
|
||||
top: 0.,
|
||||
right: 18.,
|
||||
bottom: 18.,
|
||||
left: 18.,
|
||||
})
|
||||
.into()
|
||||
}
|
||||
|
||||
#[cfg(all(test, debug_assertions))]
|
||||
#[path = "layout_tests.rs"]
|
||||
mod layout_tests;
|
||||
|
||||
Reference in New Issue
Block a user