mirror of
https://github.com/storytold/craft-libs.git
synced 2026-10-11 17:37:26 +00:00
UI.7: resize accordion bodies with pointer and keyboard handles
This commit is contained in:
1 parent
efc2fc6702
commit
c4fbe220bc
3 files changed
+79
-2
No files matched your search
@@ -246,7 +246,8 @@ impl<P: Clone + Eq + Hash + Debug + Send + Sync + 'static> Renderer<'_, P> {
|
||||
let total_wanted: f64 = entries.iter().filter(|e| e.open).map(|e| f64::from(e.height.unwrap_or(self.style.min_pane).max(1.0))).sum();
|
||||
let header = self.style.tab_height.min(rect.height() / entries.len().max(1) as f32);
|
||||
let mut top = rect.top();
|
||||
for entry in entries {
|
||||
let mut dividers = Vec::new();
|
||||
for (index, entry) in entries.iter().enumerate() {
|
||||
let share = if entry.open && total_wanted > 0.0 {
|
||||
(f64::from(remaining) * f64::from(entry.height.unwrap_or(self.style.min_pane).max(1.0)) / total_wanted) as f32
|
||||
} else {
|
||||
@@ -283,6 +284,42 @@ impl<P: Clone + Eq + Hash + Debug + Send + Sync + 'static> Renderer<'_, P> {
|
||||
accepts_tabs: (self.permissions)(&entry.panel).accepts_tabs,
|
||||
});
|
||||
top = panel_rect.bottom();
|
||||
if entry.open && remaining > 0.0 {
|
||||
dividers.push((index, Rect::from_min_max(pos2(rect.left(), top - 1.0), pos2(rect.right(), top + 1.0)).intersect(rect)));
|
||||
}
|
||||
}
|
||||
// Register dividers after the headers so their narrow hit regions win at the
|
||||
// shared boundary. Closed entries retain their saved sizes and are skipped.
|
||||
for (index, divider) in dividers {
|
||||
let Some(first) = entries.get(index) else { continue };
|
||||
let Some(second) = entries.iter().skip(index.saturating_add(1)).find(|entry| entry.open) else { continue };
|
||||
let a = f64::from(first.height.unwrap_or(self.style.min_pane).max(1.0));
|
||||
let b = f64::from(second.height.unwrap_or(self.style.min_pane).max(1.0));
|
||||
let scale = f64::from(remaining) / total_wanted;
|
||||
let first_size = (a * scale) as f32;
|
||||
let pair_size = ((a + b) * scale) as f32;
|
||||
let body_limits = |panel: &P| {
|
||||
let mut limits = (self.limits)(panel);
|
||||
limits.min.y = limits.min.y.max(self.style.min_pane);
|
||||
limits.max = limits.max.max(limits.min);
|
||||
limits
|
||||
};
|
||||
let (lo, hi) = split_bounds(pair_size, SplitAxis::Vertical, body_limits(&first.panel), body_limits(&second.panel));
|
||||
let response = ResizeHandle::new(
|
||||
self.id.with(("stack-resize", &first.panel)),
|
||||
SplitAxis::Vertical,
|
||||
divider,
|
||||
&self.style.resize_label,
|
||||
ResizeHandleStyle { hovered: self.style.accent, dragged: self.style.accent, focused: self.style.accent },
|
||||
)
|
||||
.value(first_size, lo..=hi)
|
||||
.show(ui);
|
||||
if response.delta != 0.0 && scale > 0.0 {
|
||||
let delta = f64::from((first_size + response.delta).clamp(lo, hi) - first_size) / scale;
|
||||
let next_a = (a + delta).clamp((a + b - 1_000_000.0).max(1.0), (a + b - 1.0).min(1_000_000.0));
|
||||
self.output.actions.push(Action::ResizeStack { panel: first.panel.clone(), height: Some(next_a as f32) });
|
||||
self.output.actions.push(Action::ResizeStack { panel: second.panel.clone(), height: Some((a + b - next_a) as f32) });
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -469,6 +469,46 @@ fn focused_floating_resize_grip_accepts_both_arrow_axes_and_releases_tab() {
|
||||
assert_ne!(ctx.memory(|memory| memory.focused()), Some(id));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn stack_dividers_resize_adjacent_open_bodies_by_pointer_and_keyboard() {
|
||||
let ctx = Context::default();
|
||||
let mut layout = Layout {
|
||||
root: Some(Node::Stack {
|
||||
entries: vec![
|
||||
StackEntry { panel: 1, open: true, height: Some(150.0) },
|
||||
StackEntry { panel: 2, open: false, height: Some(91.0) },
|
||||
StackEntry { panel: 3, open: true, height: Some(150.0) },
|
||||
StackEntry { panel: 4, open: true, height: Some(120.0) },
|
||||
],
|
||||
}),
|
||||
floating: Vec::new(),
|
||||
};
|
||||
for _ in 0..3 {
|
||||
step(&ctx, &mut layout, Vec::new());
|
||||
}
|
||||
let id = Id::new("test-area").with(("stack-resize", &1u32));
|
||||
let start = ctx.read_response(id).unwrap().rect.center();
|
||||
step(&ctx, &mut layout, vec![Event::PointerMoved(start), pointer_button(start, true)]);
|
||||
step(&ctx, &mut layout, vec![Event::PointerMoved(start + vec2(0.0, 24.0))]);
|
||||
step(&ctx, &mut layout, vec![pointer_button(start + vec2(0.0, 24.0), false)]);
|
||||
let Node::Stack { entries } = layout.root.as_ref().unwrap() else { panic!() };
|
||||
let after_pointer = entries[0].height.unwrap();
|
||||
assert!(after_pointer > 150.0);
|
||||
assert!((entries[0].height.unwrap() + entries[2].height.unwrap() - 300.0).abs() < 0.001);
|
||||
assert_eq!(entries[1].height, Some(91.0));
|
||||
assert!(!entries[1].open);
|
||||
assert_eq!(entries[3].height, Some(120.0));
|
||||
ctx.memory_mut(|memory| memory.request_focus(id));
|
||||
step(
|
||||
&ctx,
|
||||
&mut layout,
|
||||
vec![Event::Key { key: egui::Key::ArrowDown, physical_key: None, pressed: true, repeat: false, modifiers: Modifiers::NONE }],
|
||||
);
|
||||
let Node::Stack { entries } = layout.root.as_ref().unwrap() else { panic!() };
|
||||
assert!(entries[0].height.unwrap() > after_pointer);
|
||||
assert!((entries[0].height.unwrap() + entries[2].height.unwrap() - 300.0).abs() < 0.001);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn restoring_a_panel_beside_a_nested_subtree_preserves_the_complete_workspace() {
|
||||
let mut layout = Layout {
|
||||
|
||||
+1
-1
@@ -57,7 +57,7 @@ accepts tabs or splits. Supply targets back-to-front, including floating-window
|
||||
The frontmost containing group blocks targets underneath even when it rejects a drop.
|
||||
|
||||
The optional `DockArea` renderer handles tab fitting and overflow, drag/drop previews,
|
||||
Escape/focus-loss cancellation, split dividers and floating-group resize grips. It renders a
|
||||
Escape/focus-loss cancellation, split/accordion dividers and floating-group resize grips. It renders a
|
||||
snapshot and returns `DockOutput.actions`; apply them through the application's command path.
|
||||
With `serde` enabled, `Action` also serializes so pointer, keyboard and control-channel requests
|
||||
can share that command path, including splitter changes, tab reorder and floating bounds.
|
||||
|
||||
Reference in new issue
Block a user