mirror of
https://github.com/emilk/egui.git
synced 2026-06-27 15:13:12 -04:00
<!-- Please read the "Making a PR" section of [`CONTRIBUTING.md`](https://github.com/emilk/egui/blob/master/CONTRIBUTING.md) before opening a Pull Request! * Keep your PR:s small and focused. * The PR title is what ends up in the changelog, so make it descriptive! * If applicable, add a screenshot or gif. * If it is a non-trivial addition, consider adding a demo for it to `egui_demo_lib`, or a new example. * Do NOT open PR:s from your `master` branch, as that makes it hard for maintainers to test and add commits to your PR. * Remember to run `cargo fmt` and `cargo clippy`. * Open the PR as a draft until you have self-reviewed it and run `./scripts/check.sh`. * When you have addressed a PR comment, mark it as resolved. Please be patient! I will review your PR, but my time is limited! --> * Closes N/A, but this is part of https://github.com/emilk/egui/issues/3378 * [x] I have followed the instructions in the PR template Other text layout libraries in Rust--namely, Parley and Cosmic Text--have one canonical text cursor type (Parley's is a byte index, Cosmic Text's also stores the line index). To prepare for migrating egui to one of those libraries, it should also have only one text cursor type. I also think simplifying the API is a good idea in and of itself--having three different cursor types that you have to convert between (and a `Cursor` struct which contains all three at once) is confusing. After a bit of experimentation, I found that the best cursor type to coalesce around is `CCursor`. In the few places where we need a paragraph index or row/column position, we can calculate them as necessary. I've removed `CursorRange` and `PCursorRange` (the latter appears to have never been used), merging the functionality with `CCursorRange`. To preserve the cursor position when navigating row-by-row, `CCursorRange` now stores the previous horizontal position of the cursor. I've also removed `PCursor`, and renamed `RowCursor` to `LayoutCursor` (since it includes not only the row but the column). I have not renamed either `CCursorRange` or `CCursor` as those names are used in a lot of places, and I don't want to clutter this PR with a bunch of renames. I'll leave it for a later PR. Finally, I've removed the deprecated methods from `TextEditState`--it made the refactoring easier, and it should be pretty easy to migrate to the equivalent `TextCursorState` methods. I'm not sure how many breaking changes people will actually encounter. A lot of these APIs were technically public, but I don't think many were useful. The `TextBuffer` trait now takes `&CCursorRange` instead of `&CursorRange` in a couple of methods, and I renamed `CCursorRange::sorted` to `CCursorRange::sorted_cursors` to match `CursorRange`. I did encounter a couple of apparent minor bugs when testing out text cursor behavior, but I checked them against the current version of egui and they're all pre-existing.
88 lines
2.5 KiB
Rust
88 lines
2.5 KiB
Rust
//! Different types of text cursors, i.e. ways to point into a [`super::Galley`].
|
|
|
|
/// Character cursor.
|
|
///
|
|
/// The default cursor is zero.
|
|
#[derive(Clone, Copy, Debug, Default)]
|
|
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
|
|
pub struct CCursor {
|
|
/// Character offset (NOT byte offset!).
|
|
pub index: usize,
|
|
|
|
/// If this cursors sits right at the border of a wrapped row break (NOT paragraph break)
|
|
/// do we prefer the next row?
|
|
/// This is *almost* always what you want, *except* for when
|
|
/// explicitly clicking the end of a row or pressing the end key.
|
|
pub prefer_next_row: bool,
|
|
}
|
|
|
|
impl CCursor {
|
|
#[inline]
|
|
pub fn new(index: usize) -> Self {
|
|
Self {
|
|
index,
|
|
prefer_next_row: false,
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Two `CCursor`s are considered equal if they refer to the same character boundary,
|
|
/// even if one prefers the start of the next row.
|
|
impl PartialEq for CCursor {
|
|
#[inline]
|
|
fn eq(&self, other: &Self) -> bool {
|
|
self.index == other.index
|
|
}
|
|
}
|
|
|
|
impl std::ops::Add<usize> for CCursor {
|
|
type Output = Self;
|
|
|
|
fn add(self, rhs: usize) -> Self::Output {
|
|
Self {
|
|
index: self.index.saturating_add(rhs),
|
|
prefer_next_row: self.prefer_next_row,
|
|
}
|
|
}
|
|
}
|
|
|
|
impl std::ops::Sub<usize> for CCursor {
|
|
type Output = Self;
|
|
|
|
fn sub(self, rhs: usize) -> Self::Output {
|
|
Self {
|
|
index: self.index.saturating_sub(rhs),
|
|
prefer_next_row: self.prefer_next_row,
|
|
}
|
|
}
|
|
}
|
|
|
|
impl std::ops::AddAssign<usize> for CCursor {
|
|
fn add_assign(&mut self, rhs: usize) {
|
|
self.index = self.index.saturating_add(rhs);
|
|
}
|
|
}
|
|
|
|
impl std::ops::SubAssign<usize> for CCursor {
|
|
fn sub_assign(&mut self, rhs: usize) {
|
|
self.index = self.index.saturating_sub(rhs);
|
|
}
|
|
}
|
|
|
|
/// Row/column cursor.
|
|
///
|
|
/// This refers to rows and columns in layout terms--text wrapping creates multiple rows.
|
|
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
|
|
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
|
|
pub struct LayoutCursor {
|
|
/// 0 is first row, and so on.
|
|
/// Note that a single paragraph can span multiple rows.
|
|
/// (a paragraph is text separated by `\n`).
|
|
pub row: usize,
|
|
|
|
/// Character based (NOT bytes).
|
|
/// It is fine if this points to something beyond the end of the current row.
|
|
/// When moving up/down it may again be within the next row.
|
|
pub column: usize,
|
|
}
|