Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Cursor rework #170

Open
wants to merge 15 commits into
base: main
Choose a base branch
from
Open

Cursor rework #170

wants to merge 15 commits into from

Conversation

dfrg
Copy link
Collaborator

@dfrg dfrg commented Nov 11, 2024

This is a draft because some functionality (mostly around word/line selection) is still missing. I also commented out the fix from #163 because the skip_mandatory_break flag is now gone and I haven't dug into the logic from that PR.

Submitting this now so that people can take a look and see if it fixes some of the known bugs, particularly around newline handling.

note: #166 should probably land first

@nicoburns
Copy link
Contributor

I also commented out the fix from #163 because the skip_mandatory_break flag is now gone and I haven't dug into the logic from that PR.

Looks like you're now appending the newline cluster to the previous line rather than waiting to append it to the next line. We'll need to test, but I suspect that means that the bug fixed in #163 won't be present. The key thing not being which line the newline cluster ends up in, but that the cluster is "committed" eagerly rather than deferred to the next iteration of the loop (the fix in #163 was handling that deferred cluster commit handling the case that the next iteration of the loop was an inline box (and thus went down a different code path)).

If you wanted to keep the "newline is at the start of the following line" behaviour then think you could probably just reverse the append_cluster_to_line and try_commit_line statements (although IMO it's more intuitive if the newline cluster is part of the line it ends).

Copy link
Contributor

@nicoburns nicoburns left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've left some review comments on the layout code that I'm familiar with. I haven't reviewed the selection code which I don't fully understand yet.

parley/src/layout/line/greedy.rs Outdated Show resolved Hide resolved
Comment on lines +256 to 259
let whitespace = cluster.info().whitespace();
let is_newline = whitespace == Whitespace::Newline;
let is_space = whitespace.is_space_or_nbsp();
let boundary = cluster.info().boundary();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you explain why you have switched from Boundary::Mandatory to Whitespace::Newline here? What (if anything) is the difference?

I'm assuming the reason you (can safely) check Boundary::Line before checking is_newline is because a newline will never be a Boundary::Line (it will always be a Boundary::Mandatory)? If this is not the case then I think you need to invert the order in which you check those conditions.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The full explanation is at #132 (comment)

tldr; Unicode LBA specifies a break after the newline and a trailing newline doesn’t have a following cluster so we lose that information.

Re check order: good question. I’m going to assume that a newline sequence is not marked as a break opportunity because the code appears to work as is :)

parley/src/shape.rs Outdated Show resolved Hide resolved
parley/src/layout/line/greedy.rs Outdated Show resolved Hide resolved
@spirali
Copy link
Contributor

spirali commented Nov 11, 2024

I did some quick manual testing with 220f1bc and it seems that problem from #163 is gone. The logic behind #163 was to solve the problem that newline cluster is part of the next line and it broke placing inline boxes.

@dfrg
Copy link
Collaborator Author

dfrg commented Nov 11, 2024

I did some quick manual testing with 220f1bc and it seems that problem from #163 is gone. The logic behind #163 was to solve the problem that newline cluster is part of the next line and it broke placing inline boxes.

Thanks for confirming.

@dfrg
Copy link
Collaborator Author

dfrg commented Nov 11, 2024

If you wanted to keep the "newline is at the start of the following line" behaviour then think you could probably just reverse the append_cluster_to_line and try_commit_line statements (although IMO it's more intuitive if the newline cluster is part of the line it ends).

This was initially done to force a line to be created at the end of the layout when the last character was a newline. This didn’t really work anyway and I agree that it’s more intuitive (and correct!) to keep the newline attached to the line it ends.

@mwcampbell
Copy link
Contributor

Cursor::to_access_position returns None when the cursor is at the end of the text (e.g. after pressing Ctrl+End in vello_editor). This is incorrect, as it means we can't report an accessible text selection when the cursor is at the end.

@dfrg
Copy link
Collaborator Author

dfrg commented Nov 21, 2024

Ah, I see. We won’t have a downstream cluster at the end of the text. I’ll push a fix for this in a bit.

- correct condition for empty text in shaping
- compute actual layout height based on line max coordinates
- try to handle end of text conditions in AccessKit methods
@mwcampbell
Copy link
Contributor

Just tried the latest push with a screen reader. If I go to the end of the default text in vello_editor with Ctrl+End, then the accessible selection is where it should be. But if I then press Enter to start a new blank line at the end, then the accessible selection is on the previous line, after the hard line break, rather than on the new, blank line.

- use from_byte_index in from_accesskit instead of setting index directly to capture correct affinity for trailing newline
@dfrg
Copy link
Collaborator Author

dfrg commented Nov 21, 2024

This is probably a question of affinity. I pushed an update that might address it.

@mwcampbell
Copy link
Contributor

No change to the accessibility behavior when inserting a new blank line at the end.

@dfrg
Copy link
Collaborator Author

dfrg commented Nov 21, 2024

I wonder if that problem is that there's no actual character on that line and AccessKit doesn't have a text run node associated with it?

@mwcampbell
Copy link
Contributor

When inserting a blank line at the end of the text, is there a Parley run for that blank line at the end? It appears that there is now parley run, and an AccessKit text run node, for the one blank line if the text is empty.

@dfrg
Copy link
Collaborator Author

dfrg commented Nov 21, 2024

I think there's one if the text is empty but there's no run for a final blank line in non-empty text. I guess we need to add one somehow and make sure it's mapped properly in the to/from acessskit methods.

@dfrg dfrg marked this pull request as ready for review November 21, 2024 17:17
@dfrg dfrg changed the title WIP cursor rework Cursor rework Nov 21, 2024
Copy link
Member

@xorgy xorgy left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Glad to see this coming along. :+ )

parley/src/layout/cursor.rs Outdated Show resolved Hide resolved
parley/src/layout/cursor.rs Outdated Show resolved Hide resolved
parley/src/layout/cursor.rs Outdated Show resolved Hide resolved
Comment on lines +606 to +613
// let range = self
// .selection
// .focus()
// .cluster_path()
// .cluster(&self.layout)
// .map(|c| c.text_range())
// .unwrap_or_default();
ActiveText::FocusedCluster(self.selection.focus().affinity(), "")
Copy link
Member

@xorgy xorgy Nov 21, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👁️ 👁️ Needs reintroduced.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have some new debugging code that is more useful so I'll come up with a way to fit that in.

DJMcNab added a commit to DJMcNab/xilem that referenced this pull request Nov 22, 2024
I've had to add a hack to workaround a (new)? Parley bug where an empty initial text causes a crash
Note that later setting the text to be empty does not reproduce the crash
Copy link
Member

@DJMcNab DJMcNab left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've ported to this in linebender/xilem#755. Overall, this works well, except for the mentioned privacy concerns.

Additionally, there seems to be a new crash if the layout/buffer is exactly empty when layout is called, but only the first time.
That is, having an empty initial text crashes, but editing so that the text is empty does not. I have not dug into this very far.

parley/src/layout/editor.rs Outdated Show resolved Hide resolved
// For newline sequences and emoji, delete the previous cluster
range.start
} else {
// Otherwise, delete the previous character
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// Otherwise, delete the previous character
// Otherwise, delete the previous character (unicode scalar value)

self.editor.cursor_mode,
false,
));
self.editor.set_selection(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a pre-existing concern, but I'll raise it now; should these methods call refresh_layout? That would be necessary if specifically the text has changed since the last relayout, and I don't think anything protects against that.

}
}

pub(crate) fn maybe_extend(&self, focus: Cursor, extend: bool) -> Self {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A function of this name used to be public; it's used within editor.rs (with extend: true). Can we just expose an extend function?

@mwcampbell
Copy link
Contributor

Is the fix for the problem with accessibility on the last line waiting on something from me?

@dfrg
Copy link
Collaborator Author

dfrg commented Nov 26, 2024

Is the fix for the problem with accessibility on the last line waiting on something from me?

No, I plan on adding an empty run to the last line to see if that helps. Just haven’t gotten to it yet. I’ll ping here when the PR is updated.

@PoignardAzur
Copy link
Contributor

Pressing Delete while the cursor is a the beginning of a line puts the cursor in an invalid position.

... also various fixes from review feedback
@@ -394,10 +372,12 @@ impl<'a, B: Brush> Cluster<'a, B> {
/// Determines how a cursor attaches to a cluster.
#[derive(Copy, Clone, PartialEq, Eq, Default, Debug)]
pub enum Affinity {
/// Left side for LTR clusters and right side for RTL clusters.
/// Cursor is attached to the character that is logically following in the
/// text stream.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice! This is so much better.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Ctrl-End followed by Ctrl-Del makes the vello_editor example panic
7 participants