iris: DragArbiter closes I5's touch-drag pan-vs-select gap
A row's own click_or_drag() selection handler always won the same gesture a list-level pan wanted, since run_sensors gives the inner layer first refusal every frame it's pressed. DragArbiter (iris/src/sense.rs) decides pan vs. select the way Android does: vertical drag pans immediately, a held stationary press starts a selection after LONG_PRESS, and a horizontal drag on already-selected text extends immediately. transcript-ui's Selection::drag routes every row's drag through one arbiter per list, driving List::scroll for a pan instead of a second scroll mechanism. 8 new unit tests (iris::sense::drag_arbiter_tests); cargo fmt/clippy/test --workspace and cargo ndk (iris, transcript-ui) all clean; run-headless.sh screenshot byte-identical to before the change. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
1 parent
a853eb5a4d
commit
e5880c33f4
7 files changed
+486
-79
No files matched your search
@@ -20,7 +20,7 @@ use crate::markdown::render_markdown;
|
||||
use crate::selection::Selection;
|
||||
use client_core::transcript_fold::{QuestionCard, TranscriptItem, TranscriptRow as FoldedRow};
|
||||
use iris::prelude::*;
|
||||
use std::{cell::RefCell, rc::Rc};
|
||||
use std::{cell::RefCell, rc::Rc, time::Instant};
|
||||
|
||||
/// The paragraph size every row's `TextEdit` is built at; markdown headings
|
||||
/// inside a row scale relative to a fixed set of sizes rather than this one
|
||||
@@ -100,11 +100,13 @@ fn tool_call_markdown(tool: &str, input: &str, output: &str) -> String {
|
||||
|
||||
/// Build one `TextEdit` from a sender label plus markdown source, register
|
||||
/// it with `selection` under `key`, and wire the pointer handlers that
|
||||
/// drive `Selection::begin`/`extend` -- shared by every row variant below,
|
||||
/// since a selectable row is always "one TextEdit plus this wiring"
|
||||
/// regardless of what folded it.
|
||||
/// drive `Selection::drag` -- shared by every row variant below, since a
|
||||
/// selectable row is always "one TextEdit plus this wiring" regardless of
|
||||
/// what folded it. `list` is threaded through so that same drag can pan
|
||||
/// the list instead of selecting, per `Selection::drag`'s own doc.
|
||||
fn build_text_row<Rsc: HasEvents>(
|
||||
rsc: &mut Rsc,
|
||||
list: WeakWidget<List>,
|
||||
selection: Rc<RefCell<Selection>>,
|
||||
key: RowKey,
|
||||
sender: Option<&str>,
|
||||
@@ -125,18 +127,24 @@ where
|
||||
selection.borrow_mut().register(key, field);
|
||||
|
||||
field
|
||||
.on(CursorSense::click_or_drag(), move |ctx, rsc| {
|
||||
let sel = selection.clone();
|
||||
match ctx.data.sense {
|
||||
CursorSense::PressStart(_) => {
|
||||
sel.borrow_mut()
|
||||
.begin(rsc, key, ctx.data.pos, ctx.data.size)
|
||||
}
|
||||
_ => sel
|
||||
.borrow_mut()
|
||||
.extend(rsc, key, ctx.data.pos, ctx.data.size),
|
||||
}
|
||||
})
|
||||
// `| CursorSense::unclick()` on top of the usual click-or-drag set
|
||||
// -- the arbiter inside `Selection::drag` needs the release too,
|
||||
// to go back to idle for the next press (`DragArbiter::release`).
|
||||
.on(
|
||||
CursorSense::click_or_drag() | CursorSense::unclick(),
|
||||
move |ctx, rsc| {
|
||||
selection.borrow_mut().drag(
|
||||
rsc,
|
||||
list,
|
||||
key,
|
||||
ctx.data.pos,
|
||||
ctx.data.size,
|
||||
ctx.data.cursor.pos,
|
||||
ctx.data.sense,
|
||||
Instant::now(),
|
||||
);
|
||||
},
|
||||
)
|
||||
.add(rsc);
|
||||
|
||||
// `.add` (weak), not `.add_strong` -- `header` is about to be embedded
|
||||
@@ -165,6 +173,7 @@ where
|
||||
|
||||
fn build_single<Rsc: HasEvents>(
|
||||
rsc: &mut Rsc,
|
||||
list: WeakWidget<List>,
|
||||
selection: Rc<RefCell<Selection>>,
|
||||
key: RowKey,
|
||||
item: &TranscriptItem,
|
||||
@@ -173,7 +182,7 @@ where
|
||||
Rsc::State: FocusHost,
|
||||
{
|
||||
let (sender, markdown_src) = item_content(item);
|
||||
build_text_row(rsc, selection, key, sender, &markdown_src)
|
||||
build_text_row(rsc, list, selection, key, sender, &markdown_src)
|
||||
}
|
||||
|
||||
/// A run of adjacent tool calls: collapsed to a one-line summary by
|
||||
@@ -215,6 +224,7 @@ where
|
||||
|
||||
fn build_content<Rsc: HasEvents>(
|
||||
rsc: &mut Rsc,
|
||||
list: WeakWidget<List>,
|
||||
selection: Rc<RefCell<Selection>>,
|
||||
key: RowKey,
|
||||
expanded: bool,
|
||||
@@ -225,11 +235,12 @@ where
|
||||
Rsc::State: FocusHost,
|
||||
{
|
||||
let text = if expanded { full } else { summary };
|
||||
build_text_row(rsc, selection, key, Some("Tools"), text)
|
||||
build_text_row(rsc, list, selection, key, Some("Tools"), text)
|
||||
}
|
||||
|
||||
let content = build_content(
|
||||
rsc,
|
||||
list,
|
||||
selection.clone(),
|
||||
key,
|
||||
false,
|
||||
@@ -252,6 +263,7 @@ where
|
||||
*expanded.borrow_mut() = !was_expanded;
|
||||
let content = build_content(
|
||||
rsc,
|
||||
list,
|
||||
selection.clone(),
|
||||
key,
|
||||
!was_expanded,
|
||||
@@ -279,7 +291,7 @@ where
|
||||
match row {
|
||||
FoldedRow::Single(item) => {
|
||||
let key = row_key(&item.key());
|
||||
(key, build_single(rsc, selection, key, item))
|
||||
(key, build_single(rsc, list, selection, key, item))
|
||||
}
|
||||
FoldedRow::Tools(calls) => {
|
||||
let key = row_key(&calls[0].key());
|
||||
|
||||
Reference in new issue
Block a user