| ... | @@ -624,6 +624,11 @@ enum History { | ... | @@ -624,6 +624,11 @@ enum History { |
| 624 | image: ExGuid, | 624 | image: ExGuid, |
| 625 | layout: onestore::document::Layout, | 625 | layout: onestore::document::Layout, |
| 626 | }, | 626 | }, |
| | 627 | /// Restores the picture at `index` in paint order, or removes the one there when `None`. |
| | 628 | Picture { |
| | 629 | index: usize, |
| | 630 | image: Option<Box<onestore::page::Image>>, |
| | 631 | }, |
| 627 | Remove { | 632 | Remove { |
| 628 | outline: onestore::ExGuid, | 633 | outline: onestore::ExGuid, |
| 629 | focus: RestoreFocus, | 634 | focus: RestoreFocus, |
| ... | @@ -1336,6 +1341,62 @@ impl CanvasEditor { | ... | @@ -1336,6 +1341,62 @@ impl CanvasEditor { |
| 1336 | Ok(()) | 1341 | Ok(()) |
| 1337 | } | 1342 | } |
| 1338 | | 1343 | |
| | 1344 | /// Leaves a picture as OneNote's Left and Right arrows do: the caret goes to the end of the |
| | 1345 | /// text outline before it in page order, or to the start of the one after. False when there |
| | 1346 | /// is none. |
| | 1347 | pub fn step_from_image( |
| | 1348 | &mut self, |
| | 1349 | engine: &mut TextEngine, |
| | 1350 | id: ExGuid, |
| | 1351 | forward: bool, |
| | 1352 | ) -> Result<bool, EditorError> { |
| | 1353 | let index = self |
| | 1354 | .objects |
| | 1355 | .iter() |
| | 1356 | .position(|object| matches!(object, page::Content::Image(image) if image.id == id)) |
| | 1357 | .ok_or(EditError::InvalidRange)?; |
| | 1358 | let editable = |object: &page::Content| match object { |
| | 1359 | page::Content::Editable(outline) => Some(*outline), |
| | 1360 | _ => None, |
| | 1361 | }; |
| | 1362 | let outline = if forward { |
| | 1363 | self.objects[index + 1..].iter().find_map(editable) |
| | 1364 | } else { |
| | 1365 | self.objects[..index].iter().rev().find_map(editable) |
| | 1366 | }; |
| | 1367 | let Some(outline) = outline else { |
| | 1368 | return Ok(false); |
| | 1369 | }; |
| | 1370 | self.focus_outline(outline)?; |
| | 1371 | let edge = if forward { |
| | 1372 | Movement::DocumentStart |
| | 1373 | } else { |
| | 1374 | Movement::DocumentEnd |
| | 1375 | }; |
| | 1376 | self.move_selection(engine, edge, false)?; |
| | 1377 | Ok(true) |
| | 1378 | } |
| | 1379 | |
| | 1380 | pub fn remove_image(&mut self, id: ExGuid) -> Result<(), EditError> { |
| | 1381 | let index = self |
| | 1382 | .objects |
| | 1383 | .iter() |
| | 1384 | .position(|object| { |
| | 1385 | matches!(object, page::Content::Image(image) if image.id == id && !image.background) |
| | 1386 | }) |
| | 1387 | .ok_or(EditError::InvalidRange)?; |
| | 1388 | self.finish_composition(); |
| | 1389 | let page::Content::Image(image) = self.objects.remove(index) else { |
| | 1390 | unreachable!() |
| | 1391 | }; |
| | 1392 | self.undo.push(History::Picture { |
| | 1393 | index, |
| | 1394 | image: Some(Box::new(image)), |
| | 1395 | }); |
| | 1396 | self.redo.clear(); |
| | 1397 | Ok(()) |
| | 1398 | } |
| | 1399 | |
| 1339 | pub fn selection(&self) -> Selection { | 1400 | pub fn selection(&self) -> Selection { |
| 1340 | self.active_outline().selection | 1401 | self.active_outline().selection |
| 1341 | } | 1402 | } |
| ... | @@ -2315,6 +2376,13 @@ impl CanvasEditor { | ... | @@ -2315,6 +2376,13 @@ impl CanvasEditor { |
| 2315 | self.outlines.iter().any(|item| item.id == *outline) | 2376 | self.outlines.iter().any(|item| item.id == *outline) |
| 2316 | } | 2377 | } |
| 2317 | History::Image { image, .. } => self.image(*image).is_some(), | 2378 | History::Image { image, .. } => self.image(*image).is_some(), |
| | 2379 | History::Picture { |
| | 2380 | index, |
| | 2381 | image: Some(_), |
| | 2382 | } => *index <= self.objects.len(), |
| | 2383 | History::Picture { index, image: None } => { |
| | 2384 | matches!(self.objects.get(*index), Some(page::Content::Image(_))) |
| | 2385 | } |
| 2318 | History::Remove { outline, focus } => { | 2386 | History::Remove { outline, focus } => { |
| 2319 | self.outlines.iter().any(|item| item.id == *outline) | 2387 | self.outlines.iter().any(|item| item.id == *outline) |
| 2320 | && match focus { | 2388 | && match focus { |
| ... | @@ -2466,6 +2534,19 @@ impl CanvasEditor { | ... | @@ -2466,6 +2534,19 @@ impl CanvasEditor { |
| 2466 | image, | 2534 | image, |
| 2467 | layout: std::mem::replace(&mut self.image_mut(image).unwrap().layout, layout), | 2535 | layout: std::mem::replace(&mut self.image_mut(image).unwrap().layout, layout), |
| 2468 | }, | 2536 | }, |
| | 2537 | History::Picture { index, image } => History::Picture { |
| | 2538 | index, |
| | 2539 | image: match image { |
| | 2540 | Some(image) => { |
| | 2541 | self.objects.insert(index, page::Content::Image(*image)); |
| | 2542 | None |
| | 2543 | } |
| | 2544 | None => match self.objects.remove(index) { |
| | 2545 | page::Content::Image(image) => Some(Box::new(image)), |
| | 2546 | _ => unreachable!(), |
| | 2547 | }, |
| | 2548 | }, |
| | 2549 | }, |
| 2469 | History::Remove { outline, focus } => { | 2550 | History::Remove { outline, focus } => { |
| 2470 | let index = self | 2551 | let index = self |
| 2471 | .outlines | 2552 | .outlines |
| ... | @@ -4479,6 +4560,88 @@ mod tests { | ... | @@ -4479,6 +4560,88 @@ mod tests { |
| 4479 | assert!(editor.redo(&mut engine).unwrap()); | 4560 | assert!(editor.redo(&mut engine).unwrap()); |
| 4480 | assert!(editor.redo(&mut engine).unwrap()); | 4561 | assert!(editor.redo(&mut engine).unwrap()); |
| 4481 | assert_eq!(stored(&editor), resized); | 4562 | assert_eq!(stored(&editor), resized); |
| | 4563 | |
| | 4564 | let ids = |editor: &CanvasEditor| { |
| | 4565 | editor |
| | 4566 | .page() |
| | 4567 | .unwrap() |
| | 4568 | .objects |
| | 4569 | .iter() |
| | 4570 | .map(PageObject::id) |
| | 4571 | .collect::<Vec<_>>() |
| | 4572 | }; |
| | 4573 | let before = ids(&editor); |
| | 4574 | assert_eq!( |
| | 4575 | editor.remove_image(background_id), |
| | 4576 | Err(EditError::InvalidRange) |
| | 4577 | ); |
| | 4578 | editor.remove_image(id).unwrap(); |
| | 4579 | assert!(editor.image_placement(id).is_none()); |
| | 4580 | assert_eq!(ids(&editor), [background_id]); |
| | 4581 | assert!(editor.undo(&mut engine).unwrap()); |
| | 4582 | assert_eq!(ids(&editor), before); |
| | 4583 | assert_eq!(stored(&editor), resized); |
| | 4584 | assert!(editor.redo(&mut engine).unwrap()); |
| | 4585 | assert_eq!(ids(&editor), [background_id]); |
| | 4586 | } |
| | 4587 | |
| | 4588 | #[test] |
| | 4589 | fn arrows_leave_a_picture_for_the_neighbouring_outlines_in_page_order() { |
| | 4590 | use onestore::page::{Image, Page, PageObject}; |
| | 4591 | let mut engine = TextEngine::default(); |
| | 4592 | let outline = |engine: &mut TextEngine, text: &str, x| { |
| | 4593 | PageObject::Outline( |
| | 4594 | TextOutline::new( |
| | 4595 | engine, |
| | 4596 | TextDocument::new(vec![Paragraph::new(text.into(), Default::default())]) |
| | 4597 | .unwrap(), |
| | 4598 | 120.0, |
| | 4599 | [x, 0.0], |
| | 4600 | ) |
| | 4601 | .unwrap() |
| | 4602 | .snapshot(), |
| | 4603 | ) |
| | 4604 | }; |
| | 4605 | let picture = Image { |
| | 4606 | size: None, |
| | 4607 | id: onestore::page::text::new_id().unwrap(), |
| | 4608 | layout: onestore::document::Layout { |
| | 4609 | x: Some(200.0), |
| | 4610 | y: Some(0.0), |
| | 4611 | max_width: Some(40.0), |
| | 4612 | max_height: Some(40.0), |
| | 4613 | ..Default::default() |
| | 4614 | }, |
| | 4615 | bytes: Some(std::sync::Arc::from(b"deferred image payload".as_slice())), |
| | 4616 | alt: None, |
| | 4617 | background: false, |
| | 4618 | }; |
| | 4619 | let id = picture.id; |
| | 4620 | let objects = vec![ |
| | 4621 | outline(&mut engine, "Before", 0.0), |
| | 4622 | PageObject::Image(picture), |
| | 4623 | outline(&mut engine, "After", 300.0), |
| | 4624 | ]; |
| | 4625 | let [before, after] = [0, 2].map(|index| objects[index].id()); |
| | 4626 | let mut editor = CanvasEditor::from_page( |
| | 4627 | Page { |
| | 4628 | title: String::new(), |
| | 4629 | identity: None, |
| | 4630 | created: None, |
| | 4631 | margin_origin: [36.0, 14.4], |
| | 4632 | definitions: BTreeMap::new(), |
| | 4633 | objects, |
| | 4634 | }, |
| | 4635 | &mut engine, |
| | 4636 | ) |
| | 4637 | .unwrap(); |
| | 4638 | let caret = |paragraph, offset| [TextPosition { paragraph, offset }; 2]; |
| | 4639 | assert!(editor.step_from_image(&mut engine, id, false).unwrap()); |
| | 4640 | assert_eq!(editor.active_outline().id, before); |
| | 4641 | assert_eq!(editor.selection().positions, caret(0, 6)); |
| | 4642 | assert!(editor.step_from_image(&mut engine, id, true).unwrap()); |
| | 4643 | assert_eq!(editor.active_outline().id, after); |
| | 4644 | assert_eq!(editor.selection().positions, caret(0, 0)); |
| 4482 | } | 4645 | } |
| 4483 | | 4646 | |
| 4484 | #[test] | 4647 | #[test] |