R5.6: ElyShow right-click menus, PowerPoint-style; Impress table borders drag; R3 PASS
The canvas offers Ely's menu by target: slide (layouts, comment), text (bullets, numbering, comment), shape or text box, picture (replace, crop), table (insert, delete, merge), each item gated by the kernel's state for its own command. Impress's table borders read as text and drag through R5.3's path; patch 0047 reports every edge's bounds and makes the left edge drag. A line past its bound never moves against the drag, so a still press sends nothing. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
411d32b97e
commit
35a7bbb904
21 files changed
+920
-24
No files matched your search
@@ -18,6 +18,10 @@ pub enum Target {
|
||||
Cell,
|
||||
/// A drawing object outside Writer.
|
||||
Object,
|
||||
/// A picture outside Writer.
|
||||
Picture,
|
||||
/// An Impress slide, nothing selected.
|
||||
Slide,
|
||||
}
|
||||
|
||||
/// A misspelled word's menu: suggestions as the kernel's rules.
|
||||
@@ -92,8 +96,12 @@ pub fn read(json: &str) -> io::Result<Offered> {
|
||||
Target::Frame
|
||||
} else if enabled.contains_key(".uno:InsertCell") {
|
||||
Target::Cell
|
||||
} else if enabled.contains_key(".uno:ChangePicture") {
|
||||
Target::Picture
|
||||
} else if enabled.contains_key(".uno:ArrangeMenu") {
|
||||
Target::Object
|
||||
} else if enabled.contains_key(".uno:SlideLayoutMenu") {
|
||||
Target::Slide
|
||||
} else {
|
||||
Target::Text
|
||||
};
|
||||
|
||||
@@ -38,6 +38,11 @@ fn the_menu_names_its_target_and_flags() {
|
||||
assert_eq!(read(&object).expect("parses").target, Target::Object);
|
||||
let text = format!(r#"{{"menu":[{}]}}"#, item(".uno:Paste", true));
|
||||
assert_eq!(read(&text).expect("parses").target, Target::Text);
|
||||
// Impress: a picture also arranges; a bare slide has layouts.
|
||||
let picture = format!(r#"{{"menu":[{},{}]}}"#, item(".uno:ChangePicture", true), item(".uno:ArrangeMenu", true));
|
||||
assert_eq!(read(&picture).expect("parses").target, Target::Picture);
|
||||
let slide = format!(r#"{{"menu":[{},{}]}}"#, item(".uno:Paste", true), item(".uno:SlideLayoutMenu", true));
|
||||
assert_eq!(read(&slide).expect("parses").target, Target::Slide);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
//! The cursor's table borders, as Writer reports them (DOC-07).
|
||||
//! A table's borders, as Writer and Impress report them (DOC-07).
|
||||
|
||||
use std::io;
|
||||
|
||||
@@ -15,6 +15,8 @@ pub struct Lines {
|
||||
pub right: i32,
|
||||
pub offset: i32,
|
||||
pub inner: Vec<Line>,
|
||||
/// Bounds of the first and last lines, when sent.
|
||||
pub ends: Option<[(i32, i32); 2]>,
|
||||
}
|
||||
|
||||
/// An inner line and the kernel's bounds for it.
|
||||
@@ -41,22 +43,47 @@ pub fn read(text: &str) -> Result<Option<Table>, String> {
|
||||
if object.is_empty() {
|
||||
return Ok(None);
|
||||
}
|
||||
let int = |value: Option<&Value>| i32::try_from(value?.as_i64()?).ok();
|
||||
// Impress writes every value as text (boost's property tree).
|
||||
let int = |value: Option<&Value>| match value? {
|
||||
Value::Number(number) => i32::try_from(number.as_i64()?).ok(),
|
||||
Value::String(text) => text.parse().ok(),
|
||||
_ => None,
|
||||
};
|
||||
let flag = |value: Option<&Value>| match value? {
|
||||
Value::Bool(on) => Some(*on),
|
||||
Value::String(text) => text.parse().ok(),
|
||||
_ => None,
|
||||
};
|
||||
let lines = |key: &str| -> Option<Lines> {
|
||||
let axis = object.get(key)?;
|
||||
let inner = axis.get("entries")?.as_array()?.iter().map(|entry| {
|
||||
// Boost writes an empty list as an empty string.
|
||||
let entries = match axis.get("entries")? {
|
||||
Value::Array(entries) => entries.as_slice(),
|
||||
Value::String(text) if text.is_empty() => &[],
|
||||
_ => return None,
|
||||
};
|
||||
let inner = entries.iter().map(|entry| {
|
||||
Some(Line {
|
||||
position: int(entry.get("position"))?,
|
||||
min: int(entry.get("min"))?,
|
||||
max: int(entry.get("max"))?,
|
||||
hidden: entry.get("hidden")?.as_bool()?,
|
||||
hidden: flag(entry.get("hidden"))?,
|
||||
})
|
||||
});
|
||||
let bound = |key: &str| int(axis.get(key));
|
||||
let ends = match ["leftMin", "leftMax", "rightMin", "rightMax"].map(|key| axis.get(key).is_some()) {
|
||||
[false, false, false, false] => None,
|
||||
[true, true, true, true] => {
|
||||
Some([(bound("leftMin")?, bound("leftMax")?), (bound("rightMin")?, bound("rightMax")?)])
|
||||
}
|
||||
_ => return None,
|
||||
};
|
||||
Some(Lines {
|
||||
left: int(axis.get("left"))?,
|
||||
right: int(axis.get("right"))?,
|
||||
offset: int(axis.get("tableOffset"))?,
|
||||
inner: inner.collect::<Option<_>>()?,
|
||||
ends,
|
||||
})
|
||||
};
|
||||
let columns = lines("columns").ok_or_else(fault)?;
|
||||
@@ -75,6 +102,10 @@ impl Table {
|
||||
.i64(axis.right.into())
|
||||
.i64(axis.offset.into())
|
||||
.u64(axis.inner.len() as u64);
|
||||
let pack = match axis.ends {
|
||||
None => pack.u64(0),
|
||||
Some(ends) => ends.iter().fold(pack.u64(1), |pack, &(low, high)| pack.i64(low.into()).i64(high.into())),
|
||||
};
|
||||
axis.inner.iter().fold(pack, |pack, line| {
|
||||
pack.i64(line.position.into())
|
||||
.i64(line.min.into())
|
||||
@@ -91,14 +122,22 @@ impl Table {
|
||||
let mut lines = || -> io::Result<Lines> {
|
||||
let mut int = || i32::try_from(unpack.i64()?).map_err(io::Error::other);
|
||||
let (left, right, offset) = (int()?, int()?, int()?);
|
||||
let inner = (0..unpack.u64()?)
|
||||
let count = unpack.u64()?;
|
||||
let ends = match unpack.u64()? {
|
||||
0 => None,
|
||||
_ => {
|
||||
let mut int = || i32::try_from(unpack.i64()?).map_err(io::Error::other);
|
||||
Some([(int()?, int()?), (int()?, int()?)])
|
||||
}
|
||||
};
|
||||
let inner = (0..count)
|
||||
.map(|_| {
|
||||
let mut int = || i32::try_from(unpack.i64()?).map_err(io::Error::other);
|
||||
let (position, min, max) = (int()?, int()?, int()?);
|
||||
Ok(Line { position, min, max, hidden: unpack.u64()? != 0 })
|
||||
})
|
||||
.collect::<io::Result<_>>()?;
|
||||
Ok(Lines { left, right, offset, inner })
|
||||
Ok(Lines { left, right, offset, inner, ends })
|
||||
};
|
||||
let columns = lines()?;
|
||||
let rows = lines()?;
|
||||
@@ -112,14 +151,41 @@ mod tests {
|
||||
|
||||
const SEEN: &str = r#"{ "columns": { "left": 0, "right": 9638, "tableOffset": 1135, "entries": [ { "position": 4819, "min": 300, "max": 9338, "hidden": false } ] }, "rows": { "left": 0, "right": 552, "tableOffset": 1418, "entries": [ { "position": 276, "min": 100, "max": 552, "hidden": false } ] } }"#;
|
||||
|
||||
/// As Impress sent it for a new 2x2 table.
|
||||
const IMPRESS: &str = r#"{ "columns": { "tableOffset": "3929", "left": "0", "right": "7993", "entries": [ { "position": "3996", "min": "340", "max": "7653", "hidden": "false" } ] }, "rows": { "tableOffset": "4340", "left": "0", "right": "1091", "entries": [ { "position": "545", "min": "340", "max": "152183880", "hidden": "false" } ] }, "rectangle": { "x": "3940", "y": "4351", "width": "7993", "height": "1091" } }"#;
|
||||
|
||||
#[test]
|
||||
fn impress_writes_numbers_as_text() {
|
||||
let table = read(IMPRESS).expect("readable").expect("a table");
|
||||
assert_eq!(table.columns.offset, 3929);
|
||||
assert_eq!(table.columns.inner, [Line { position: 3996, min: 340, max: 7653, hidden: false }]);
|
||||
assert_eq!(table.rows.right, 1091);
|
||||
assert!(read(&IMPRESS.replace("\"false\"", "\"no\"")).is_err(), "a flag is true or false");
|
||||
assert_eq!(table.rows.ends, None, "an unpatched kernel sends no ends");
|
||||
}
|
||||
|
||||
/// One row, as Impress sends it with patch 0047.
|
||||
const ONE_ROW: &str = r#"{ "columns": { "tableOffset": "3929", "left": "0", "right": "7993", "leftMin": "-152183880", "leftMax": "7823", "rightMin": "170", "rightMax": "152183880", "entries": "" }, "rows": { "tableOffset": "4340", "left": "0", "right": "545", "leftMin": "-152183880", "leftMax": "28", "rightMin": "517", "rightMax": "152183880", "entries": "" } }"#;
|
||||
|
||||
#[test]
|
||||
fn one_line_has_no_inner_lines_and_its_ends_cross_the_wire() {
|
||||
let table = read(ONE_ROW).expect("readable").expect("a table");
|
||||
assert!(table.columns.inner.is_empty() && table.rows.inner.is_empty());
|
||||
assert_eq!(table.rows.ends, Some([(-152_183_880, 28), (517, 152_183_880)]));
|
||||
assert!(read(&ONE_ROW.replace("\"entries\": \"\"", "\"entries\": \"x\"")).is_err(), "text is no list");
|
||||
assert!(read(&ONE_ROW.replace("\"leftMin\": \"-152183880\", ", "")).is_err(), "ends come in fours");
|
||||
let bytes = Table::pack(Some(&table), Pack::new()).bytes();
|
||||
assert_eq!(Table::unpack(&mut Unpack::new(&bytes)).expect("unpacks"), Some(table));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_table_reads_and_crosses_the_wire() {
|
||||
assert_eq!(read("{ }"), Ok(None), "no table");
|
||||
let table = read(SEEN).expect("readable").expect("a table");
|
||||
assert_eq!(table.columns.offset, 1135);
|
||||
assert_eq!(table.rows.inner[0].position, 276);
|
||||
let text = SEEN.replace("\"left\": 0,", "\"left\": \"0\",");
|
||||
assert!(read(&text).is_err(), "a number as text is a fault");
|
||||
let text = SEEN.replace("\"left\": 0,", "\"left\": \"zero\",");
|
||||
assert!(read(&text).is_err(), "a word is no number");
|
||||
assert!(read("[]").is_err());
|
||||
let bytes = Table::pack(Some(&table), Pack::new()).bytes();
|
||||
let back = Table::unpack(&mut Unpack::new(&bytes)).expect("unpacks");
|
||||
|
||||
@@ -99,15 +99,23 @@ fn inner(lines: &Lines) -> impl Iterator<Item = (u16, i32)> + '_ {
|
||||
fn clamp(table: &Table, side: Side, offset: i32) -> i32 {
|
||||
let axis = if side.upright() { &table.columns } else { &table.rows };
|
||||
let (first, last) = (axis.inner.first(), axis.inner.last());
|
||||
let (at, low, high) = match side {
|
||||
Side::ColumnLeft => (axis.left, i32::MIN, first.map_or(axis.right, |line| line.position) - GAP),
|
||||
Side::ColumnRight | Side::RowBottom => (axis.right, last.map_or(axis.left, |line| line.position) + GAP, i32::MAX),
|
||||
Side::Column(index) | Side::Row(index) => {
|
||||
// Impress sends its outer bounds; Writer leaves a gap.
|
||||
let (at, low, high) = match (side, axis.ends) {
|
||||
(Side::ColumnLeft, Some([(low, high), _])) => (axis.left, low, high),
|
||||
(Side::ColumnRight | Side::RowBottom, Some([_, (low, high)])) => (axis.right, low, high),
|
||||
(Side::ColumnLeft, None) => (axis.left, i32::MIN, first.map_or(axis.right, |line| line.position) - GAP),
|
||||
(Side::ColumnRight | Side::RowBottom, None) => {
|
||||
(axis.right, last.map_or(axis.left, |line| line.position) + GAP, i32::MAX)
|
||||
}
|
||||
(Side::Column(index) | Side::Row(index), ends) => {
|
||||
let line = axis.inner[usize::from(index)];
|
||||
(line.position, line.min, (line.max - GAP).max(line.min))
|
||||
let high = if ends.is_some() { line.max } else { (line.max - GAP).max(line.min) };
|
||||
(line.position, line.min, high)
|
||||
}
|
||||
};
|
||||
(at.saturating_add(offset)).clamp(low, high) - at
|
||||
let moved = (at.saturating_add(offset)).clamp(low, high) - at;
|
||||
// A line past its bound never moves against the drag.
|
||||
if moved.signum() == offset.signum() { moved } else { 0 }
|
||||
}
|
||||
|
||||
impl LokView {
|
||||
@@ -229,8 +237,8 @@ mod tests {
|
||||
fn table() -> Table {
|
||||
let line = |position, min, max| Line { position, min, max, hidden: false };
|
||||
Table {
|
||||
columns: Lines { left: 0, right: 9000, offset: 1000, inner: vec![line(3000, 300, 6000), line(6000, 3300, 8700)] },
|
||||
rows: Lines { left: 0, right: 800, offset: 2000, inner: vec![line(400, 100, 800)] },
|
||||
columns: Lines { left: 0, right: 9000, offset: 1000, inner: vec![line(3000, 300, 6000), line(6000, 3300, 8700)], ends: None },
|
||||
rows: Lines { left: 0, right: 800, offset: 2000, inner: vec![line(400, 100, 800)], ends: None },
|
||||
}
|
||||
}
|
||||
|
||||
@@ -261,4 +269,25 @@ mod tests {
|
||||
assert_eq!(clamp(&table, Side::RowBottom, -800), -360, "a row keeps the gap");
|
||||
assert_eq!(clamp(&table, Side::Row(0), 500), 360, "max less the gap");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn sent_ends_bound_the_outer_lines() {
|
||||
let mut table = table();
|
||||
table.columns.ends = Some([(-50_000, 2800), (6200, 50_000)]);
|
||||
table.rows.ends = Some([(-50_000, 380), (717, 50_000)]);
|
||||
assert_eq!(clamp(&table, Side::ColumnLeft, 5000), 2800, "to the first column's minimum");
|
||||
assert_eq!(clamp(&table, Side::ColumnLeft, -900), -900, "outward too");
|
||||
assert_eq!(clamp(&table, Side::ColumnRight, -8000), -2800, "to the last column's minimum");
|
||||
assert_eq!(clamp(&table, Side::RowBottom, -800), -83, "to the last row's minimum");
|
||||
assert_eq!(clamp(&table, Side::Column(0), 9000), 3000, "Impress's max needs no gap");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_line_at_or_past_its_bound_stays_put() {
|
||||
let mut table = table();
|
||||
table.columns.inner[0].max = 3000;
|
||||
assert_eq!(clamp(&table, Side::Column(0), 0), 0, "a still press sends nothing");
|
||||
assert_eq!(clamp(&table, Side::Column(0), 200), 0, "no room toward the bound");
|
||||
assert_eq!(clamp(&table, Side::Column(0), -200), -200, "away from it is free");
|
||||
}
|
||||
}
|
||||
@@ -6,6 +6,7 @@ mod commands;
|
||||
mod fit;
|
||||
mod input;
|
||||
mod large;
|
||||
mod show_borders;
|
||||
mod theme;
|
||||
use gpui::{App, Bounds, Entity, point, size};
|
||||
|
||||
|
||||
@@ -0,0 +1,172 @@
|
||||
use super::*;
|
||||
use crate::client::tests::{poll_until, show::DECK};
|
||||
use crate::table::Table;
|
||||
use gpui::{Pixels, Point, TestAppContext};
|
||||
|
||||
const WAIT: std::time::Duration = std::time::Duration::from_secs(5);
|
||||
|
||||
/// A line's place along its axis, in twips.
|
||||
type Place = fn(&Table) -> i32;
|
||||
|
||||
/// A new table on the slide, read into the view.
|
||||
fn table_in(view: &mut LokView, (columns, rows): (&str, &str)) -> Table {
|
||||
let doc = view.doc.clone();
|
||||
let mut worker = view.worker.lock().expect("the worker lock");
|
||||
worker.run(&doc, "table.insert", &[columns, rows]).expect("a table");
|
||||
let seen = poll_until(&mut worker, &doc, &Events::default(), WAIT, |seen| seen.table.is_some());
|
||||
assert!(seen.faults.is_empty(), "{:?}", seen.faults);
|
||||
let table = seen.table.expect("the selected table");
|
||||
view.doc = worker.now(&doc).expect("the revision");
|
||||
drop(worker);
|
||||
view.read_borders(Some(table.clone()), view.doc.rev);
|
||||
table
|
||||
}
|
||||
|
||||
/// The view's table once `place` moves off `from`.
|
||||
fn moved(view: &mut LokView, place: Place, from: i32) -> Table {
|
||||
let mut worker = view.worker.lock().expect("the worker lock");
|
||||
let seen = poll_until(&mut worker, &view.doc, &Events::default(), WAIT, |seen| {
|
||||
seen.table.as_ref().is_some_and(|table| place(table) != from)
|
||||
});
|
||||
let table = seen.table.expect("still the table");
|
||||
view.doc = worker.now(&view.doc).expect("the revision");
|
||||
drop(worker);
|
||||
view.read_borders(Some(table.clone()), view.doc.rev);
|
||||
table
|
||||
}
|
||||
|
||||
/// The last drag's place: before, guide, landed, undone.
|
||||
fn drag(size: (&str, &str), place: Place, upright: bool, bys: &[Point<Pixels>]) -> Option<[i32; 4]> {
|
||||
let install = crate::client::tests::install()?;
|
||||
let cx = TestAppContext::single();
|
||||
let (view, _) = cx.update(|app| live_view_of(app, &install, "deck.fodp", DECK));
|
||||
let mut table = cx.update(|app| view.update(app, |view, _| table_in(view, size)));
|
||||
let mut last = [0; 3];
|
||||
for &by in bys {
|
||||
let (before, guide) = cx.update(|app| {
|
||||
view.update(app, |view, cx| {
|
||||
let at = place(&table);
|
||||
let across = if upright { &table.rows } else { &table.columns };
|
||||
// A quarter along: halfway may cross another line.
|
||||
let along = across.offset + across.left + (across.right - across.left) / 4;
|
||||
let on = view.window_point(if upright { (at, along) } else { (along, at) });
|
||||
assert!(view.hold_border(on), "the line is held");
|
||||
assert!(view.move_border(on + by));
|
||||
let guide = view.doc_point(view.guide().expect("a guide").origin);
|
||||
assert!(view.drop_border(on + by, cx), "the drop runs");
|
||||
(at, if upright { guide.0 } else { guide.1 })
|
||||
})
|
||||
});
|
||||
for _ in 0..80 {
|
||||
cx.executor().tick();
|
||||
}
|
||||
table = cx.update(|app| view.update(app, |view, _| moved(view, place, before)));
|
||||
last = [before, guide, place(&table)];
|
||||
}
|
||||
let undone = cx.update(|app| {
|
||||
view.update(app, |view, _| {
|
||||
view.worker.lock().expect("the worker lock").run(&view.doc, "edit.undo", &[]).expect("undoes");
|
||||
place(&moved(view, place, last[2]))
|
||||
})
|
||||
});
|
||||
Some([last[0], last[1], last[2], undone])
|
||||
}
|
||||
|
||||
/// Lands at its guide; one undo puts it back.
|
||||
#[track_caller]
|
||||
fn lands([before, guide, landed, undone]: [i32; 4]) {
|
||||
assert!((landed - guide).abs() <= 2, "{before} to the guide {guide}, landed {landed}");
|
||||
assert!((undone - before).abs() <= 1, "one undo: {undone} vs {before}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn an_inner_line_lands_at_its_guide() {
|
||||
let place: Place = |t| t.columns.offset + t.columns.inner[0].position;
|
||||
let Some(moved) = drag(("2", "2"), place, true, &[gpui::point(px(40.0), px(0.0))]) else {
|
||||
return eprintln!("SKIP: no pinned LOK build");
|
||||
};
|
||||
assert!(moved[1] > moved[0], "the guide went right");
|
||||
lands(moved);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn the_left_edge_narrows_the_first_column_and_moves_the_table() {
|
||||
let place: Place = |t| t.columns.offset + t.columns.left;
|
||||
let Some(moved) = drag(("2", "2"), place, true, &[gpui::point(px(40.0), px(0.0))]) else {
|
||||
return eprintln!("SKIP: no pinned LOK build");
|
||||
};
|
||||
assert!(moved[1] > moved[0], "the guide went right");
|
||||
lands(moved);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_one_row_bottom_grows() {
|
||||
let place: Place = |t| t.rows.offset + t.rows.right;
|
||||
let Some(moved) = drag(("2", "1"), place, false, &[gpui::point(px(0.0), px(40.0))]) else {
|
||||
return eprintln!("SKIP: no pinned LOK build");
|
||||
};
|
||||
assert!(moved[1] > moved[0], "the guide went down");
|
||||
lands(moved);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_grown_bottom_stops_at_the_last_rows_minimum() {
|
||||
let place: Place = |t| t.rows.offset + t.rows.right;
|
||||
let Some(moved) = drag(("2", "2"), place, false, &[gpui::point(px(0.0), px(60.0)), gpui::point(px(0.0), px(-200.0))]) else {
|
||||
return eprintln!("SKIP: no pinned LOK build");
|
||||
};
|
||||
assert!(moved[1] < moved[0] && moved[1] > moved[0] - 3000, "the guide stops at the minimum: {moved:?}");
|
||||
lands(moved);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_one_column_right_edge_stops_at_the_columns_minimum() {
|
||||
let place: Place = |t| t.columns.offset + t.columns.right;
|
||||
let Some(moved) = drag(("1", "2"), place, true, &[gpui::point(px(-600.0), px(0.0))]) else {
|
||||
return eprintln!("SKIP: no pinned LOK build");
|
||||
};
|
||||
assert!(moved[1] > moved[0] - 9000, "the guide stops at the minimum: {moved:?}");
|
||||
lands(moved);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_still_press_beside_a_minimum_column_sends_nothing() {
|
||||
let Some(install) = crate::client::tests::install() else {
|
||||
return eprintln!("SKIP: no pinned LOK build");
|
||||
};
|
||||
let cx = TestAppContext::single();
|
||||
let (view, _) = cx.update(|app| live_view_of(app, &install, "deck.fodp", DECK));
|
||||
let right: Place = |t| t.columns.offset + t.columns.right;
|
||||
let quarter = |t: &Table| t.rows.offset + t.rows.left + (t.rows.right - t.rows.left) / 4;
|
||||
let mut table = cx.update(|app| view.update(app, |view, _| table_in(view, ("2", "2"))));
|
||||
// Its minimum width drops once it narrows; shrink twice.
|
||||
for _ in 0..2 {
|
||||
let before = cx.update(|app| {
|
||||
view.update(app, |view, cx| {
|
||||
let on = view.window_point((right(&table), quarter(&table)));
|
||||
let to = on + gpui::point(px(-600.0), px(0.0));
|
||||
assert!(view.hold_border(on) && view.move_border(to) && view.drop_border(to, cx));
|
||||
right(&table)
|
||||
})
|
||||
});
|
||||
for _ in 0..80 {
|
||||
cx.executor().tick();
|
||||
}
|
||||
table = cx.update(|app| view.update(app, |view, _| moved(view, right, before)));
|
||||
}
|
||||
let sent = cx.update(|app| {
|
||||
view.update(app, |view, cx| {
|
||||
let line = table.columns.inner[0];
|
||||
assert_eq!(line.position, line.max, "the right column is at its minimum");
|
||||
let on = view.window_point((table.columns.offset + line.position, quarter(&table)));
|
||||
let sent = view.sent();
|
||||
assert!(view.hold_border(on));
|
||||
assert!(view.drop_border(on, cx));
|
||||
sent
|
||||
})
|
||||
});
|
||||
for _ in 0..80 {
|
||||
cx.executor().tick();
|
||||
}
|
||||
assert_eq!(cx.update(|app| view.read(app).sent()), sent, "a still press sends no command");
|
||||
}
|
||||
Reference in new issue
Block a user