1
0
mirror of https://github.com/emilk/egui.git synced 2026-08-31 05:40:03 -04:00

Use new type Estring to avoid cloning &'static str

`ui.label("static string")` is a very common use case,
and currently egui clones the string in these cases.

This PR introduces a new type:

``` rust
pub enum Estring {
    Static(&'static str),
    Owned(Arc<str>),
}
```

which is used everywhere text is needed, with
`impl Into<Estring>` in the API for e.g. `ui.label`.

This reduces the number of copies drastically and speeds up
the benchmark demo_with_tessellate__realistic by 17%.

This hurts the ergonomics of egui a bit, and this is a breaking change.

For instance, this used to work:

``` rust
fn my_label(ui: &mut egui::Ui, text: &str) {
    ui.label(text);
}
```

This must now either be changed to

``` rust
fn my_label(ui: &mut egui::Ui, text: &str) {
    ui.label(text.to_string());
}
```

(or the argument must be changed to either
`text: &'static str` or `text: String`)
This commit is contained in:
Emil Ernerfeldt
2021-09-03 22:26:24 +02:00
parent 3b75a84d3b
commit b3e41e4e9c
36 changed files with 413 additions and 225 deletions

View File

@@ -1,5 +1,5 @@
use crate::{
text::{Fonts, Galley, TextStyle},
text::{Estring, Fonts, Galley, TextStyle},
Color32, Mesh, Stroke,
};
use emath::*;
@@ -166,16 +166,15 @@ impl Shape {
}
}
#[allow(clippy::needless_pass_by_value)]
pub fn text(
fonts: &Fonts,
pos: Pos2,
anchor: Align2,
text: impl ToString,
text: impl Into<Estring>,
text_style: TextStyle,
color: Color32,
) -> Self {
let galley = fonts.layout_no_wrap(text.to_string(), text_style, color);
let galley = fonts.layout_no_wrap(text.into(), text_style, color);
let rect = anchor.anchor_rect(Rect::from_min_size(pos, galley.size));
Self::galley(rect.min, galley)
}

173
epaint/src/text/estring.rs Normal file
View File

@@ -0,0 +1,173 @@
use std::sync::Arc;
/// An immutable string, backed by either `&'static str` or `Arc<String>`.
///
/// Wherever you see `impl Into<Estring>` pass either a `String` or
/// a `&'static str` (a `"string literal"`).
///
/// Estring provides fast `Clone`.
#[derive(Clone)]
pub enum Estring {
Static(&'static str),
Owned(Arc<str>),
}
impl Estring {
#[inline]
pub fn as_str(&self) -> &str {
match self {
Self::Static(s) => s,
Self::Owned(s) => s,
}
}
#[inline]
pub fn is_empty(&self) -> bool {
self.as_str().is_empty()
}
#[inline]
pub fn len(&self) -> usize {
self.as_str().len()
}
}
impl Default for Estring {
fn default() -> Self {
Self::Static("")
}
}
impl std::convert::AsRef<str> for Estring {
fn as_ref(&self) -> &str {
self.as_str()
}
}
impl std::borrow::Borrow<str> for Estring {
fn borrow(&self) -> &str {
self.as_str()
}
}
impl std::hash::Hash for Estring {
#[inline]
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
self.as_str().hash(state)
}
}
impl PartialEq for Estring {
#[inline]
fn eq(&self, other: &Self) -> bool {
self.as_str() == other.as_str()
}
}
impl std::cmp::Eq for Estring {}
impl std::cmp::PartialOrd for Estring {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
Some(self.cmp(other))
}
}
impl std::cmp::Ord for Estring {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
self.as_str().cmp(other.as_str())
}
}
impl std::fmt::Display for Estring {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
self.as_str().fmt(f)
}
}
impl std::fmt::Debug for Estring {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
self.as_str().fmt(f)
}
}
// ----------------------------------------------------------------------------
impl std::convert::From<&'static str> for Estring {
fn from(s: &'static str) -> Self {
Self::Static(s)
}
}
impl std::convert::From<String> for Estring {
fn from(s: String) -> Self {
Self::Owned(s.into())
}
}
impl std::convert::From<&String> for Estring {
fn from(s: &String) -> Self {
Self::Owned(s.clone().into())
}
}
impl std::convert::From<&Estring> for Estring {
fn from(s: &Estring) -> Self {
s.clone()
}
}
// ----------------------------------------------------------------------------
impl std::ops::Index<std::ops::Range<usize>> for Estring {
type Output = str;
#[inline]
fn index(&self, index: std::ops::Range<usize>) -> &str {
self.as_str().index(index)
}
}
impl std::ops::Index<std::ops::RangeTo<usize>> for Estring {
type Output = str;
#[inline]
fn index(&self, index: std::ops::RangeTo<usize>) -> &str {
self.as_str().index(index)
}
}
impl std::ops::Index<std::ops::RangeFrom<usize>> for Estring {
type Output = str;
#[inline]
fn index(&self, index: std::ops::RangeFrom<usize>) -> &str {
self.as_str().index(index)
}
}
impl std::ops::Index<std::ops::RangeFull> for Estring {
type Output = str;
#[inline]
fn index(&self, index: std::ops::RangeFull) -> &str {
self.as_str().index(index)
}
}
impl std::ops::Index<std::ops::RangeInclusive<usize>> for Estring {
type Output = str;
#[inline]
fn index(&self, index: std::ops::RangeInclusive<usize>) -> &str {
self.as_str().index(index)
}
}
impl std::ops::Index<std::ops::RangeToInclusive<usize>> for Estring {
type Output = str;
#[inline]
fn index(&self, index: std::ops::RangeToInclusive<usize>) -> &str {
self.as_str().index(index)
}
}

View File

@@ -10,7 +10,7 @@ use crate::{
mutex::Mutex,
text::{
font::{Font, FontImpl},
Galley, LayoutJob,
Estring, Galley, LayoutJob,
},
Texture, TextureAtlas,
};
@@ -330,7 +330,7 @@ impl Fonts {
/// The implementation uses memoization so repeated calls are cheap.
pub fn layout(
&self,
text: String,
text: impl Into<Estring>,
text_style: TextStyle,
color: crate::Color32,
wrap_width: f32,
@@ -344,7 +344,7 @@ impl Fonts {
/// The implementation uses memoization so repeated calls are cheap.
pub fn layout_no_wrap(
&self,
text: String,
text: impl Into<Estring>,
text_style: TextStyle,
color: crate::Color32,
) -> Arc<Galley> {
@@ -357,7 +357,7 @@ impl Fonts {
/// The implementation uses memoization so repeated calls are cheap.
pub fn layout_delayed_color(
&self,
text: String,
text: impl Into<Estring>,
text_style: TextStyle,
wrap_width: f32,
) -> Arc<Galley> {

View File

@@ -1,6 +1,7 @@
//! Everything related to text, fonts, text layout, cursors etc.
pub mod cursor;
mod estring;
mod font;
mod fonts;
mod text_layout;
@@ -10,6 +11,7 @@ mod text_layout_types;
pub const TAB_SIZE: usize = 4;
pub use {
estring::Estring,
fonts::{FontDefinitions, FontFamily, Fonts, TextStyle},
text_layout::layout,
text_layout_types::*,

View File

@@ -1,7 +1,7 @@
use std::ops::Range;
use std::sync::Arc;
use super::{cursor::*, font::UvRect};
use super::{cursor::*, font::UvRect, Estring};
use crate::{Color32, Mesh, Stroke, TextStyle};
use emath::*;
@@ -13,7 +13,7 @@ use emath::*;
#[derive(Clone, Debug)]
pub struct LayoutJob {
/// The complete text of this job, referenced by `LayoutSection`.
pub text: String, // TODO: Cow<'static, str>
pub text: Estring,
/// The different section, which can have different fonts, colors, etc.
pub sections: Vec<LayoutSection>,
@@ -53,7 +53,13 @@ impl Default for LayoutJob {
impl LayoutJob {
/// Break on `\n` and at the given wrap width.
#[inline]
pub fn simple(text: String, text_style: TextStyle, color: Color32, wrap_width: f32) -> Self {
pub fn simple(
text: impl Into<Estring>,
text_style: TextStyle,
color: Color32,
wrap_width: f32,
) -> Self {
let text = text.into();
Self {
sections: vec![LayoutSection {
leading_space: 0.0,
@@ -69,7 +75,12 @@ impl LayoutJob {
/// Does not break on `\n`, but shows the replacement character instead.
#[inline]
pub fn simple_singleline(text: String, text_style: TextStyle, color: Color32) -> Self {
pub fn simple_singleline(
text: impl Into<Estring>,
text_style: TextStyle,
color: Color32,
) -> Self {
let text = text.into();
Self {
sections: vec![LayoutSection {
leading_space: 0.0,
@@ -87,18 +98,6 @@ impl LayoutJob {
pub fn is_empty(&self) -> bool {
self.sections.is_empty()
}
/// Helper for adding a new section when building a `LayoutJob`.
pub fn append(&mut self, text: &str, leading_space: f32, format: TextFormat) {
let start = self.text.len();
self.text += text;
let byte_range = start..self.text.len();
self.sections.push(LayoutSection {
leading_space,
byte_range,
format,
});
}
}
impl std::hash::Hash for LayoutJob {
@@ -135,6 +134,37 @@ impl std::cmp::Eq for LayoutJob {}
// ----------------------------------------------------------------------------
/// Build a [`LayoutJob`] from many small pieces.
#[derive(Clone, Debug, Default)]
pub struct LayoutJobBuilder {
text: String,
sections: Vec<LayoutSection>,
}
impl LayoutJobBuilder {
/// Helper for adding a new section when building a `LayoutJob`.
pub fn append(&mut self, text: &str, leading_space: f32, format: TextFormat) {
let start = self.text.len();
self.text += text;
let byte_range = start..self.text.len();
self.sections.push(LayoutSection {
leading_space,
byte_range,
format,
});
}
pub fn build(self) -> LayoutJob {
LayoutJob {
text: self.text.into(),
sections: self.sections,
..Default::default()
}
}
}
// ----------------------------------------------------------------------------
#[derive(Clone, Debug)]
pub struct LayoutSection {
/// Can be used for first row indentation.
@@ -366,7 +396,7 @@ impl Galley {
#[inline(always)]
pub fn text(&self) -> &str {
&self.job.text
self.job.text.as_str()
}
}