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:
@@ -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
173
epaint/src/text/estring.rs
Normal 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)
|
||||
}
|
||||
}
|
||||
@@ -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> {
|
||||
|
||||
@@ -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::*,
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user