Skip to content

Commit 51e5fad

Browse files
committed
fix(iced): scope tab release fallback
1 parent 0abd3e4 commit 51e5fad

4 files changed

Lines changed: 59 additions & 11 deletions

File tree

crates/roost-iced/src/app.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3857,6 +3857,10 @@ impl App {
38573857
);
38583858
}
38593859

3860+
pub(crate) fn has_tab_drag_preview(&self) -> bool {
3861+
self.tab_drag_preview.is_some()
3862+
}
3863+
38603864
pub(crate) fn tab_strip_event(&mut self, event: TabStripEvent) {
38613865
match event {
38623866
TabStripEvent::Started {

crates/roost-iced/src/main.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -214,7 +214,7 @@ fn update(app: &mut App, message: Message) -> Task<Message> {
214214
}
215215

216216
fn view(app: &App) -> iced::Element<'_, Message> {
217-
tab_reorder::ReleaseBoundary::new(app.view()).into()
217+
tab_reorder::ReleaseBoundary::new(app.view(), app.has_tab_drag_preview()).into()
218218
}
219219

220220
fn subscription(_app: &App) -> Subscription<Message> {

crates/roost-iced/src/tab_reorder.rs

Lines changed: 50 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -13,31 +13,37 @@ use crate::Message;
1313
const DRAG_THRESHOLD: f32 = 8.0;
1414

1515
/// Provides a same-event root release fallback after direct child ownership.
16-
/// The boundary delegates without changing capture, then publishes even when a
17-
/// reflowed scrollable withheld the event from the tab strip.
16+
/// The boundary delegates without changing capture, then publishes for an
17+
/// application-owned preview even when a reflowed scrollable withheld the
18+
/// event from the tab strip.
1819
pub(crate) struct ReleaseBoundary<'a> {
1920
content: Element<'a, Message>,
21+
enabled: bool,
2022
}
2123

2224
impl<'a> ReleaseBoundary<'a> {
23-
pub(crate) fn new(content: impl Into<Element<'a, Message>>) -> Self {
25+
pub(crate) fn new(content: impl Into<Element<'a, Message>>, enabled: bool) -> Self {
2426
Self {
2527
content: content.into(),
28+
enabled,
2629
}
2730
}
2831
}
2932

3033
fn release_after_child<Message>(
3134
event: &Event,
3235
shell: &mut Shell<'_, Message>,
36+
enabled: bool,
3337
release: impl FnOnce() -> Message,
3438
update_child: impl FnOnce(&mut Shell<'_, Message>),
3539
) {
3640
update_child(shell);
37-
if matches!(
38-
event,
39-
Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Left))
40-
) {
41+
if enabled
42+
&& matches!(
43+
event,
44+
Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Left))
45+
)
46+
{
4147
tracing::debug!("Iced root observed left-button release");
4248
shell.publish(release());
4349
}
@@ -95,6 +101,7 @@ impl Widget<Message, iced::Theme, iced::Renderer> for ReleaseBoundary<'_> {
95101
release_after_child(
96102
event,
97103
shell,
104+
self.enabled,
98105
|| Message::TabPointerReleased,
99106
|shell| {
100107
content.as_widget_mut().update(
@@ -709,6 +716,7 @@ mod tests {
709716
release_after_child(
710717
&release,
711718
&mut shell,
719+
true,
712720
|| "root",
713721
|shell| {
714722
shell.publish("child");
@@ -726,6 +734,7 @@ mod tests {
726734
release_after_child(
727735
&release,
728736
&mut shell,
737+
true,
729738
|| "root",
730739
|shell| {
731740
shell.publish("ignored child");
@@ -735,6 +744,40 @@ mod tests {
735744
};
736745
assert_eq!(messages, ["ignored child", "root"]);
737746
assert_eq!(status, iced::event::Status::Ignored);
747+
748+
messages.clear();
749+
let status = {
750+
let mut shell = Shell::new(&mut messages);
751+
release_after_child(
752+
&release,
753+
&mut shell,
754+
false,
755+
|| "root",
756+
|shell| {
757+
shell.publish("disabled child");
758+
shell.capture_event();
759+
},
760+
);
761+
shell.event_status()
762+
};
763+
assert_eq!(messages, ["disabled child"]);
764+
assert_eq!(status, iced::event::Status::Captured);
765+
766+
messages.clear();
767+
let motion = Event::Mouse(mouse::Event::CursorMoved {
768+
position: Point::new(10.0, 10.0),
769+
});
770+
let mut shell = Shell::new(&mut messages);
771+
release_after_child(
772+
&motion,
773+
&mut shell,
774+
true,
775+
|| "root",
776+
|shell| {
777+
shell.publish("motion child");
778+
},
779+
);
780+
assert_eq!(messages, ["motion child"]);
738781
}
739782

740783
#[test]

docs/development/iced-poc-plan.md

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2413,9 +2413,10 @@ making press ownership stable across preview reflow.
24132413
24142414
The scrollable parent can also withhold the release from the child strip
24152415
entirely. A transparent widget at the application root must therefore delegate
2416-
the event, then unconditionally publish a same-event release fallback even if a
2417-
child captured it. Unlike a runtime-event subscription, this preserves causal
2418-
ordering: an asynchronously delayed release can never settle a later gesture.
2416+
the event, then publish a same-event release fallback for an application-owned
2417+
preview even if a child captured it. Unlike a runtime-event subscription, this
2418+
preserves causal ordering: an asynchronously delayed release can never settle a
2419+
later gesture. Ordinary terminal releases do not create application messages.
24192420
When the strip receives release, its pre-child commit is queued first; when the
24202421
scrollable withholds release, the root fallback remains. The application port
24212422
settles only the matching generation-stamped preview and reuses the same

0 commit comments

Comments
 (0)