Compare commits

...

1 commit

Author SHA1 Message Date
Emil Ernerfeldt
b3e41e4e9c 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`)
2021-09-03 22:38:00 +02:00
36 changed files with 413 additions and 225 deletions

View file

@ -152,7 +152,7 @@ impl CollapsingHeader {
/// If the label is unique and static this is fine, /// If the label is unique and static this is fine,
/// but if it changes or there are several `CollapsingHeader` with the same title /// but if it changes or there are several `CollapsingHeader` with the same title
/// you need to provide a unique id source with [`Self::id_source`]. /// you need to provide a unique id source with [`Self::id_source`].
pub fn new(label: impl ToString) -> Self { pub fn new(label: impl Into<Estring>) -> Self {
let label = Label::new(label).wrap(false); let label = Label::new(label).wrap(false);
let id_source = Id::new(label.text()); let id_source = Id::new(label.text());
Self { Self {

View file

@ -23,7 +23,7 @@ use epaint::Shape;
pub struct ComboBox { pub struct ComboBox {
id_source: Id, id_source: Id,
label: Option<Label>, label: Option<Label>,
selected_text: String, selected_text: Estring,
width: Option<f32>, width: Option<f32>,
} }
@ -56,9 +56,8 @@ impl ComboBox {
} }
/// What we show as the currently selected value /// What we show as the currently selected value
#[allow(clippy::needless_pass_by_value)] pub fn selected_text(mut self, selected_text: impl Into<Estring>) -> Self {
pub fn selected_text(mut self, selected_text: impl ToString) -> Self { self.selected_text = selected_text.into();
self.selected_text = selected_text.to_string();
self self
} }
@ -143,11 +142,10 @@ impl ComboBox {
} }
} }
#[allow(clippy::needless_pass_by_value)]
fn combo_box<R>( fn combo_box<R>(
ui: &mut Ui, ui: &mut Ui,
button_id: Id, button_id: Id,
selected: impl ToString, selected: impl Into<Estring>,
menu_contents: impl FnOnce(&mut Ui) -> R, menu_contents: impl FnOnce(&mut Ui) -> R,
) -> InnerResponse<Option<R>> { ) -> InnerResponse<Option<R>> {
let popup_id = button_id.with("popup"); let popup_id = button_id.with("popup");
@ -158,9 +156,9 @@ fn combo_box<R>(
let full_minimum_width = ui.spacing().slider_width; let full_minimum_width = ui.spacing().slider_width;
let icon_size = Vec2::splat(ui.spacing().icon_width); let icon_size = Vec2::splat(ui.spacing().icon_width);
let galley = let galley = ui
ui.fonts() .fonts()
.layout_delayed_color(selected.to_string(), TextStyle::Button, f32::INFINITY); .layout_delayed_color(selected, TextStyle::Button, f32::INFINITY);
let width = galley.size.x + ui.spacing().item_spacing.x + icon_size.x; let width = galley.size.x + ui.spacing().item_spacing.x + icon_size.x;
let width = width.at_least(full_minimum_width); let width = width.at_least(full_minimum_width);

View file

@ -194,7 +194,7 @@ fn show_tooltip_at_avoid<R>(
/// egui::show_tooltip_text(ui.ctx(), egui::Id::new("my_tooltip"), "Helpful text"); /// egui::show_tooltip_text(ui.ctx(), egui::Id::new("my_tooltip"), "Helpful text");
/// } /// }
/// ``` /// ```
pub fn show_tooltip_text(ctx: &CtxRef, id: Id, text: impl ToString) -> Option<()> { pub fn show_tooltip_text(ctx: &CtxRef, id: Id, text: impl Into<Estring>) -> Option<()> {
show_tooltip(ctx, id, |ui| { show_tooltip(ctx, id, |ui| {
ui.add(crate::widgets::Label::new(text)); ui.add(crate::widgets::Label::new(text));
}) })

View file

@ -37,9 +37,8 @@ impl<'open> Window<'open> {
/// The window title is used as a unique [`Id`] and must be unique, and should not change. /// The window title is used as a unique [`Id`] and must be unique, and should not change.
/// This is true even if you disable the title bar with `.title_bar(false)`. /// This is true even if you disable the title bar with `.title_bar(false)`.
/// If you need a changing title, you must call `window.id(…)` with a fixed id. /// If you need a changing title, you must call `window.id(…)` with a fixed id.
#[allow(clippy::needless_pass_by_value)] pub fn new(title: impl Into<Estring>) -> Self {
pub fn new(title: impl ToString) -> Self { let title = title.into();
let title = title.to_string();
let area = Area::new(&title); let area = Area::new(&title);
let title_label = Label::new(title).text_style(TextStyle::Heading).wrap(false); let title_label = Label::new(title).text_style(TextStyle::Heading).wrap(false);
Self { Self {

View file

@ -1,4 +1,4 @@
use crate::{egui_assert, emath::*, Align}; use crate::{egui_assert, emath::*, Align, Estring};
use std::f32::INFINITY; use std::f32::INFINITY;
// ---------------------------------------------------------------------------- // ----------------------------------------------------------------------------
@ -737,7 +737,7 @@ impl Layout {
painter: &crate::Painter, painter: &crate::Painter,
region: &Region, region: &Region,
stroke: epaint::Stroke, stroke: epaint::Stroke,
text: impl ToString, text: impl Into<Estring>,
) { ) {
let cursor = region.cursor; let cursor = region.cursor;
let next_pos = self.next_widget_position(region); let next_pos = self.next_widget_position(region);

View file

@ -352,7 +352,7 @@ pub use emath as math; // historical reasons
pub use emath::{lerp, pos2, remap, remap_clamp, vec2, Align, Align2, NumExt, Pos2, Rect, Vec2}; pub use emath::{lerp, pos2, remap, remap_clamp, vec2, Align, Align2, NumExt, Pos2, Rect, Vec2};
pub use epaint::{ pub use epaint::{
color, mutex, color, mutex,
text::{FontDefinitions, FontFamily, TextStyle}, text::{Estring, FontDefinitions, FontFamily, TextStyle},
ClippedMesh, Color32, Rgba, Shape, Stroke, Texture, TextureId, ClippedMesh, Color32, Rgba, Shape, Stroke, Texture, TextureId,
}; };
@ -378,7 +378,9 @@ pub use {
}; };
pub mod text { pub mod text {
pub use epaint::text::{Galley, LayoutJob, LayoutSection, TextFormat, TAB_SIZE}; pub use epaint::text::{
Galley, LayoutJob, LayoutJobBuilder, LayoutSection, TextFormat, TAB_SIZE,
};
} }
// ---------------------------------------------------------------------------- // ----------------------------------------------------------------------------

View file

@ -64,7 +64,7 @@ pub fn bar<R>(ui: &mut Ui, add_contents: impl FnOnce(&mut Ui) -> R) -> InnerResp
/// Returns `None` if the menu is not open. /// Returns `None` if the menu is not open.
pub fn menu<R>( pub fn menu<R>(
ui: &mut Ui, ui: &mut Ui,
title: impl ToString, title: impl Into<Estring>,
add_contents: impl FnOnce(&mut Ui) -> R, add_contents: impl FnOnce(&mut Ui) -> R,
) -> Option<R> { ) -> Option<R> {
menu_impl(ui, title, Box::new(add_contents)) menu_impl(ui, title, Box::new(add_contents))
@ -104,13 +104,12 @@ pub(crate) fn menu_ui<'c, R>(
}) })
} }
#[allow(clippy::needless_pass_by_value)]
fn menu_impl<'c, R>( fn menu_impl<'c, R>(
ui: &mut Ui, ui: &mut Ui,
title: impl ToString, title: impl Into<Estring>,
add_contents: Box<dyn FnOnce(&mut Ui) -> R + 'c>, add_contents: Box<dyn FnOnce(&mut Ui) -> R + 'c>,
) -> Option<R> { ) -> Option<R> {
let title = title.to_string(); let title = title.into();
let bar_id = ui.id(); let bar_id = ui.id();
let menu_id = bar_id.with(&title); let menu_id = bar_id.with(&title);

View file

@ -5,7 +5,7 @@ use crate::{
}; };
use epaint::{ use epaint::{
mutex::Mutex, mutex::Mutex,
text::{Fonts, Galley, TextStyle}, text::{Estring, Fonts, Galley, TextStyle},
Shape, Stroke, Shape, Stroke,
}; };
@ -193,17 +193,10 @@ impl Painter {
/// ## Debug painting /// ## Debug painting
impl Painter { impl Painter {
#[allow(clippy::needless_pass_by_value)] pub fn debug_rect(&mut self, rect: Rect, color: Color32, text: impl Into<Estring>) {
pub fn debug_rect(&mut self, rect: Rect, color: Color32, text: impl ToString) {
self.rect_stroke(rect, 0.0, (1.0, color)); self.rect_stroke(rect, 0.0, (1.0, color));
let text_style = TextStyle::Monospace; let text_style = TextStyle::Monospace;
self.text( self.text(rect.min, Align2::LEFT_TOP, text.into(), text_style, color);
rect.min,
Align2::LEFT_TOP,
text.to_string(),
text_style,
color,
);
} }
pub fn error(&self, pos: Pos2, text: impl std::fmt::Display) -> Rect { pub fn error(&self, pos: Pos2, text: impl std::fmt::Display) -> Rect {
@ -211,15 +204,14 @@ impl Painter {
} }
/// text with a background /// text with a background
#[allow(clippy::needless_pass_by_value)]
pub fn debug_text( pub fn debug_text(
&self, &self,
pos: Pos2, pos: Pos2,
anchor: Align2, anchor: Align2,
color: Color32, color: Color32,
text: impl ToString, text: impl Into<Estring>,
) -> Rect { ) -> Rect {
let galley = self.layout_no_wrap(text.to_string(), TextStyle::Monospace, color); let galley = self.layout_no_wrap(text, TextStyle::Monospace, color);
let rect = anchor.anchor_rect(Rect::from_min_size(pos, galley.size)); let rect = anchor.anchor_rect(Rect::from_min_size(pos, galley.size));
let frame_rect = rect.expand(2.0); let frame_rect = rect.expand(2.0);
self.add(Shape::Rect { self.add(Shape::Rect {
@ -332,16 +324,15 @@ impl Painter {
/// [`Self::layout`] or [`Self::layout_no_wrap`]. /// [`Self::layout`] or [`Self::layout_no_wrap`].
/// ///
/// Returns where the text ended up. /// Returns where the text ended up.
#[allow(clippy::needless_pass_by_value)]
pub fn text( pub fn text(
&self, &self,
pos: Pos2, pos: Pos2,
anchor: Align2, anchor: Align2,
text: impl ToString, text: impl Into<Estring>,
text_style: TextStyle, text_style: TextStyle,
text_color: Color32, text_color: Color32,
) -> Rect { ) -> Rect {
let galley = self.layout_no_wrap(text.to_string(), text_style, text_color); let galley = self.layout_no_wrap(text, text_style, text_color);
let rect = anchor.anchor_rect(Rect::from_min_size(pos, galley.size)); let rect = anchor.anchor_rect(Rect::from_min_size(pos, galley.size));
self.galley(rect.min, galley); self.galley(rect.min, galley);
rect rect
@ -353,7 +344,7 @@ impl Painter {
#[inline(always)] #[inline(always)]
pub fn layout( pub fn layout(
&self, &self,
text: String, text: impl Into<Estring>,
text_style: TextStyle, text_style: TextStyle,
color: crate::Color32, color: crate::Color32,
wrap_width: f32, wrap_width: f32,
@ -367,7 +358,7 @@ impl Painter {
#[inline(always)] #[inline(always)]
pub fn layout_no_wrap( pub fn layout_no_wrap(
&self, &self,
text: String, text: impl Into<Estring>,
text_style: TextStyle, text_style: TextStyle,
color: crate::Color32, color: crate::Color32,
) -> std::sync::Arc<Galley> { ) -> std::sync::Arc<Galley> {

View file

@ -262,7 +262,7 @@ impl Placer {
} }
impl Placer { impl Placer {
pub(crate) fn debug_paint_cursor(&self, painter: &crate::Painter, text: impl ToString) { pub(crate) fn debug_paint_cursor(&self, painter: &crate::Painter, text: impl Into<Estring>) {
let stroke = Stroke::new(1.0, Color32::DEBUG_COLOR); let stroke = Stroke::new(1.0, Color32::DEBUG_COLOR);
if let Some(grid) = &self.grid { if let Some(grid) = &self.grid {

View file

@ -1,8 +1,7 @@
use crate::{ use crate::{
emath::{lerp, Align, Pos2, Rect, Vec2}, emath::{lerp, Align, Pos2, Rect, Vec2},
CursorIcon, PointerButton, NUM_POINTER_BUTTONS, CtxRef, CursorIcon, Estring, Id, LayerId, PointerButton, Sense, Ui, NUM_POINTER_BUTTONS,
}; };
use crate::{CtxRef, Id, LayerId, Sense, Ui};
// ---------------------------------------------------------------------------- // ----------------------------------------------------------------------------
@ -373,14 +372,14 @@ impl Response {
/// For that, use [`Self::on_disabled_hover_text`] instead. /// For that, use [`Self::on_disabled_hover_text`] instead.
/// ///
/// If you call this multiple times the tooltips will stack underneath the previous ones. /// If you call this multiple times the tooltips will stack underneath the previous ones.
pub fn on_hover_text(self, text: impl ToString) -> Self { pub fn on_hover_text(self, text: impl Into<Estring>) -> Self {
self.on_hover_ui(|ui| { self.on_hover_ui(|ui| {
ui.add(crate::widgets::Label::new(text)); ui.add(crate::widgets::Label::new(text));
}) })
} }
/// Show this text when hovering if the widget is disabled. /// Show this text when hovering if the widget is disabled.
pub fn on_disabled_hover_text(self, text: impl ToString) -> Self { pub fn on_disabled_hover_text(self, text: impl Into<Estring>) -> Self {
self.on_disabled_hover_ui(|ui| { self.on_disabled_hover_ui(|ui| {
ui.add(crate::widgets::Label::new(text)); ui.add(crate::widgets::Label::new(text));
}) })

View file

@ -919,7 +919,7 @@ impl DebugOptions {
fn slider_vec2<'a>( fn slider_vec2<'a>(
value: &'a mut Vec2, value: &'a mut Vec2,
range: std::ops::RangeInclusive<f32>, range: std::ops::RangeInclusive<f32>,
text: &'a str, text: &'static str,
) -> impl Widget + 'a { ) -> impl Widget + 'a {
move |ui: &mut crate::Ui| { move |ui: &mut crate::Ui| {
ui.horizontal(|ui| { ui.horizontal(|ui| {

View file

@ -979,7 +979,7 @@ impl Ui {
/// Shortcut for `add(Hyperlink::new(url))` /// Shortcut for `add(Hyperlink::new(url))`
/// ///
/// See also [`Hyperlink`]. /// See also [`Hyperlink`].
pub fn hyperlink(&mut self, url: impl ToString) -> Response { pub fn hyperlink(&mut self, url: impl Into<Estring>) -> Response {
Hyperlink::new(url).ui(self) Hyperlink::new(url).ui(self)
} }
@ -991,7 +991,7 @@ impl Ui {
/// ``` /// ```
/// ///
/// See also [`Hyperlink`]. /// See also [`Hyperlink`].
pub fn hyperlink_to(&mut self, label: impl ToString, url: impl ToString) -> Response { pub fn hyperlink_to(&mut self, label: impl Into<Estring>, url: impl Into<Estring>) -> Response {
Hyperlink::new(url).text(label).ui(self) Hyperlink::new(url).text(label).ui(self)
} }
@ -1031,7 +1031,7 @@ impl Ui {
/// See also [`Button`]. /// See also [`Button`].
#[must_use = "You should check if the user clicked this with `if ui.button(…).clicked() { … } "] #[must_use = "You should check if the user clicked this with `if ui.button(…).clicked() { … } "]
#[inline(always)] #[inline(always)]
pub fn button(&mut self, text: impl ToString) -> Response { pub fn button(&mut self, text: impl Into<Estring>) -> Response {
Button::new(text).ui(self) Button::new(text).ui(self)
} }
@ -1041,19 +1041,19 @@ impl Ui {
/// ///
/// Shortcut for `add(Button::new(text).small())` /// Shortcut for `add(Button::new(text).small())`
#[must_use = "You should check if the user clicked this with `if ui.small_button(…).clicked() { … } "] #[must_use = "You should check if the user clicked this with `if ui.small_button(…).clicked() { … } "]
pub fn small_button(&mut self, text: impl ToString) -> Response { pub fn small_button(&mut self, text: impl Into<Estring>) -> Response {
Button::new(text).small().ui(self) Button::new(text).small().ui(self)
} }
/// Show a checkbox. /// Show a checkbox.
pub fn checkbox(&mut self, checked: &mut bool, text: impl ToString) -> Response { pub fn checkbox(&mut self, checked: &mut bool, text: impl Into<Estring>) -> Response {
Checkbox::new(checked, text).ui(self) Checkbox::new(checked, text).ui(self)
} }
/// Show a [`RadioButton`]. /// Show a [`RadioButton`].
/// Often you want to use [`Self::radio_value`] instead. /// Often you want to use [`Self::radio_value`] instead.
#[must_use = "You should check if the user clicked this with `if ui.radio(…).clicked() { … } "] #[must_use = "You should check if the user clicked this with `if ui.radio(…).clicked() { … } "]
pub fn radio(&mut self, selected: bool, text: impl ToString) -> Response { pub fn radio(&mut self, selected: bool, text: impl Into<Estring>) -> Response {
RadioButton::new(selected, text).ui(self) RadioButton::new(selected, text).ui(self)
} }
@ -1078,7 +1078,7 @@ impl Ui {
&mut self, &mut self,
current_value: &mut Value, current_value: &mut Value,
selected_value: Value, selected_value: Value,
text: impl ToString, text: impl Into<Estring>,
) -> Response { ) -> Response {
let mut response = self.radio(*current_value == selected_value, text); let mut response = self.radio(*current_value == selected_value, text);
if response.clicked() { if response.clicked() {
@ -1092,7 +1092,7 @@ impl Ui {
/// ///
/// See also [`SelectableLabel`]. /// See also [`SelectableLabel`].
#[must_use = "You should check if the user clicked this with `if ui.selectable_label(…).clicked() { … } "] #[must_use = "You should check if the user clicked this with `if ui.selectable_label(…).clicked() { … } "]
pub fn selectable_label(&mut self, checked: bool, text: impl ToString) -> Response { pub fn selectable_label(&mut self, checked: bool, text: impl Into<Estring>) -> Response {
SelectableLabel::new(checked, text).ui(self) SelectableLabel::new(checked, text).ui(self)
} }
@ -1106,7 +1106,7 @@ impl Ui {
&mut self, &mut self,
current_value: &mut Value, current_value: &mut Value,
selected_value: Value, selected_value: Value,
text: impl ToString, text: impl Into<Estring>,
) -> Response { ) -> Response {
let mut response = self.selectable_label(*current_value == selected_value, text); let mut response = self.selectable_label(*current_value == selected_value, text);
if response.clicked() { if response.clicked() {
@ -1303,7 +1303,7 @@ impl Ui {
/// A [`CollapsingHeader`] that starts out collapsed. /// A [`CollapsingHeader`] that starts out collapsed.
pub fn collapsing<R>( pub fn collapsing<R>(
&mut self, &mut self,
heading: impl ToString, heading: impl Into<Estring>,
add_contents: impl FnOnce(&mut Ui) -> R, add_contents: impl FnOnce(&mut Ui) -> R,
) -> CollapsingResponse<R> { ) -> CollapsingResponse<R> {
CollapsingHeader::new(heading).show(self, add_contents) CollapsingHeader::new(heading).show(self, add_contents)
@ -1644,7 +1644,7 @@ impl Ui {
/// Shows the given text where the next widget is to be placed /// Shows the given text where the next widget is to be placed
/// if when [`Context::set_debug_on_hover`] has been turned on and the mouse is hovering the Ui. /// if when [`Context::set_debug_on_hover`] has been turned on and the mouse is hovering the Ui.
pub fn trace_location(&self, text: impl ToString) { pub fn trace_location(&self, text: impl Into<Estring>) {
let rect = self.max_rect(); let rect = self.max_rect();
if self.style().debug.debug_on_hover && self.rect_contains_pointer(rect) { if self.style().debug.debug_on_hover && self.rect_contains_pointer(rect) {
self.placer self.placer

View file

@ -22,7 +22,7 @@ fn select<T>(b: bool, if_true: T, if_false: T) -> T {
/// ``` /// ```
#[must_use = "You should put this widget in an ui with `ui.add(widget);`"] #[must_use = "You should put this widget in an ui with `ui.add(widget);`"]
pub struct Button { pub struct Button {
text: String, text: Estring,
text_color: Option<Color32>, text_color: Option<Color32>,
text_style: Option<TextStyle>, text_style: Option<TextStyle>,
/// None means default for interact /// None means default for interact
@ -36,10 +36,9 @@ pub struct Button {
} }
impl Button { impl Button {
#[allow(clippy::needless_pass_by_value)] pub fn new(text: impl Into<Estring>) -> Self {
pub fn new(text: impl ToString) -> Self {
Self { Self {
text: text.to_string(), text: text.into(),
text_color: None, text_color: None,
text_style: None, text_style: None,
fill: None, fill: None,
@ -235,17 +234,16 @@ impl Widget for Button {
#[derive(Debug)] #[derive(Debug)]
pub struct Checkbox<'a> { pub struct Checkbox<'a> {
checked: &'a mut bool, checked: &'a mut bool,
text: String, text: Estring,
text_color: Option<Color32>, text_color: Option<Color32>,
text_style: Option<TextStyle>, text_style: Option<TextStyle>,
} }
impl<'a> Checkbox<'a> { impl<'a> Checkbox<'a> {
#[allow(clippy::needless_pass_by_value)] pub fn new(checked: &'a mut bool, text: impl Into<Estring>) -> Self {
pub fn new(checked: &'a mut bool, text: impl ToString) -> Self {
Checkbox { Checkbox {
checked, checked,
text: text.to_string(), text: text.into(),
text_color: None, text_color: None,
text_style: None, text_style: None,
} }
@ -360,17 +358,16 @@ impl<'a> Widget for Checkbox<'a> {
#[derive(Debug)] #[derive(Debug)]
pub struct RadioButton { pub struct RadioButton {
checked: bool, checked: bool,
text: String, text: Estring,
text_color: Option<Color32>, text_color: Option<Color32>,
text_style: Option<TextStyle>, text_style: Option<TextStyle>,
} }
impl RadioButton { impl RadioButton {
#[allow(clippy::needless_pass_by_value)] pub fn new(checked: bool, text: impl Into<Estring>) -> Self {
pub fn new(checked: bool, text: impl ToString) -> Self {
Self { Self {
checked, checked,
text: text.to_string(), text: text.into(),
text_color: None, text_color: None,
text_style: None, text_style: None,
} }

View file

@ -1,5 +1,3 @@
#![allow(clippy::needless_pass_by_value)] // False positives with `impl ToString`
use std::ops::RangeInclusive; use std::ops::RangeInclusive;
use crate::*; use crate::*;
@ -50,8 +48,8 @@ fn set(get_set_value: &mut GetSetValue<'_>, value: f64) {
pub struct DragValue<'a> { pub struct DragValue<'a> {
get_set_value: GetSetValue<'a>, get_set_value: GetSetValue<'a>,
speed: f64, speed: f64,
prefix: String, prefix: Estring,
suffix: String, suffix: Estring,
clamp_range: RangeInclusive<f64>, clamp_range: RangeInclusive<f64>,
min_decimals: usize, min_decimals: usize,
max_decimals: Option<usize>, max_decimals: Option<usize>,
@ -100,14 +98,14 @@ impl<'a> DragValue<'a> {
} }
/// Show a prefix before the number, e.g. "x: " /// Show a prefix before the number, e.g. "x: "
pub fn prefix(mut self, prefix: impl ToString) -> Self { pub fn prefix(mut self, prefix: impl Into<Estring>) -> Self {
self.prefix = prefix.to_string(); self.prefix = prefix.into();
self self
} }
/// Add a suffix to the number, this can be e.g. a unit ("°" or " m") /// Add a suffix to the number, this can be e.g. a unit ("°" or " m")
pub fn suffix(mut self, suffix: impl ToString) -> Self { pub fn suffix(mut self, suffix: impl Into<Estring>) -> Self {
self.suffix = suffix.to_string(); self.suffix = suffix.into();
self self
} }

View file

@ -11,32 +11,29 @@ use crate::*;
/// ``` /// ```
#[must_use = "You should put this widget in an ui with `ui.add(widget);`"] #[must_use = "You should put this widget in an ui with `ui.add(widget);`"]
pub struct Hyperlink { pub struct Hyperlink {
url: String, url: Estring,
label: Label, label: Label,
} }
impl Hyperlink { impl Hyperlink {
#[allow(clippy::needless_pass_by_value)] pub fn new(url: impl Into<Estring>) -> Self {
pub fn new(url: impl ToString) -> Self { let url = url.into();
let url = url.to_string();
Self { Self {
url: url.clone(), url: url.clone(),
label: Label::new(url).sense(Sense::click()), label: Label::new(url).sense(Sense::click()),
} }
} }
#[allow(clippy::needless_pass_by_value)] pub fn from_label_and_url(label: impl Into<Label>, url: impl Into<Estring>) -> Self {
pub fn from_label_and_url(label: impl Into<Label>, url: impl ToString) -> Self {
Self { Self {
url: url.to_string(), url: url.into(),
label: label.into(), label: label.into(),
} }
} }
/// Show some other text than the url /// Show some other text than the url
#[allow(clippy::needless_pass_by_value)] pub fn text(mut self, text: impl Into<Estring>) -> Self {
pub fn text(mut self, text: impl ToString) -> Self { self.label.text = text.into();
self.label.text = text.to_string();
self self
} }
@ -63,13 +60,13 @@ impl Widget for Hyperlink {
if response.clicked() { if response.clicked() {
let modifiers = ui.ctx().input().modifiers; let modifiers = ui.ctx().input().modifiers;
ui.ctx().output().open_url = Some(crate::output::OpenUrl { ui.ctx().output().open_url = Some(crate::output::OpenUrl {
url: url.clone(), url: url.to_string(),
new_tab: modifiers.any(), new_tab: modifiers.any(),
}); });
} }
if response.middle_clicked() { if response.middle_clicked() {
ui.ctx().output().open_url = Some(crate::output::OpenUrl { ui.ctx().output().open_url = Some(crate::output::OpenUrl {
url: url.clone(), url: url.to_string(),
new_tab: true, new_tab: true,
}); });
} }

View file

@ -16,7 +16,7 @@ use std::sync::Arc;
#[must_use = "You should put this widget in an ui with `ui.add(widget);`"] #[must_use = "You should put this widget in an ui with `ui.add(widget);`"]
pub struct Label { pub struct Label {
// TODO: not pub // TODO: not pub
pub(crate) text: String, pub(crate) text: Estring,
pub(crate) wrap: Option<bool>, pub(crate) wrap: Option<bool>,
pub(crate) text_style: Option<TextStyle>, pub(crate) text_style: Option<TextStyle>,
pub(crate) background_color: Color32, pub(crate) background_color: Color32,
@ -32,10 +32,9 @@ pub struct Label {
} }
impl Label { impl Label {
#[allow(clippy::needless_pass_by_value)] pub fn new(text: impl Into<Estring>) -> Self {
pub fn new(text: impl ToString) -> Self {
Self { Self {
text: text.to_string(), text: text.into(),
wrap: None, wrap: None,
text_style: None, text_style: None,
background_color: Color32::TRANSPARENT, background_color: Color32::TRANSPARENT,
@ -51,7 +50,7 @@ impl Label {
} }
} }
pub fn text(&self) -> &str { pub(crate) fn text(&self) -> &Estring {
&self.text &self.text
} }
@ -211,7 +210,7 @@ impl Label {
}; };
let job = LayoutJob { let job = LayoutJob {
text: self.text.clone(), // TODO: avoid clone text: self.text.clone(),
sections: vec![LayoutSection { sections: vec![LayoutSection {
leading_space, leading_space,
byte_range: 0..self.text.len(), byte_range: 0..self.text.len(),
@ -358,8 +357,20 @@ impl Widget for Label {
} }
} }
impl From<&str> for Label { impl From<&'static str> for Label {
fn from(s: &str) -> Label { fn from(s: &'static str) -> Label {
Label::new(s)
}
}
impl From<&Estring> for Label {
fn from(s: &Estring) -> Label {
Label::new(s)
}
}
impl From<Estring> for Label {
fn from(s: Estring) -> Label {
Label::new(s) Label::new(s)
} }
} }

View file

@ -92,7 +92,7 @@ pub fn reset_button<T: Default + PartialEq>(ui: &mut Ui, value: &mut T) {
// ---------------------------------------------------------------------------- // ----------------------------------------------------------------------------
pub fn stroke_ui(ui: &mut crate::Ui, stroke: &mut epaint::Stroke, text: &str) { pub fn stroke_ui(ui: &mut crate::Ui, stroke: &mut epaint::Stroke, text: &'static str) {
let epaint::Stroke { width, color } = stroke; let epaint::Stroke { width, color } = stroke;
ui.horizontal(|ui| { ui.horizontal(|ui| {
ui.add(DragValue::new(width).speed(0.1).clamp_range(0.0..=5.0)) ui.add(DragValue::new(width).speed(0.1).clamp_range(0.0..=5.0))
@ -108,7 +108,7 @@ pub fn stroke_ui(ui: &mut crate::Ui, stroke: &mut epaint::Stroke, text: &str) {
}); });
} }
pub(crate) fn shadow_ui(ui: &mut Ui, shadow: &mut epaint::Shadow, text: &str) { pub(crate) fn shadow_ui(ui: &mut Ui, shadow: &mut epaint::Shadow, text: &'static str) {
let epaint::Shadow { extrusion, color } = shadow; let epaint::Shadow { extrusion, color } = shadow;
ui.horizontal(|ui| { ui.horizontal(|ui| {
ui.label(text); ui.label(text);

View file

@ -126,7 +126,7 @@ impl ToString for LineStyle {
pub struct HLine { pub struct HLine {
pub(super) y: f64, pub(super) y: f64,
pub(super) stroke: Stroke, pub(super) stroke: Stroke,
pub(super) name: String, pub(super) name: Estring,
pub(super) highlight: bool, pub(super) highlight: bool,
pub(super) style: LineStyle, pub(super) style: LineStyle,
} }
@ -136,7 +136,7 @@ impl HLine {
Self { Self {
y: y.into(), y: y.into(),
stroke: Stroke::new(1.0, Color32::TRANSPARENT), stroke: Stroke::new(1.0, Color32::TRANSPARENT),
name: String::default(), name: Estring::default(),
highlight: false, highlight: false,
style: LineStyle::Solid, style: LineStyle::Solid,
} }
@ -178,9 +178,8 @@ impl HLine {
/// ///
/// Multiple plot items may share the same name, in which case they will also share an entry in /// Multiple plot items may share the same name, in which case they will also share an entry in
/// the legend. /// the legend.
#[allow(clippy::needless_pass_by_value)] pub fn name(mut self, name: impl Into<Estring>) -> Self {
pub fn name(mut self, name: impl ToString) -> Self { self.name = name.into();
self.name = name.to_string();
self self
} }
} }
@ -204,7 +203,7 @@ impl PlotItem for HLine {
fn initialize(&mut self, _x_range: RangeInclusive<f64>) {} fn initialize(&mut self, _x_range: RangeInclusive<f64>) {}
fn name(&self) -> &str { fn name(&self) -> &str {
&self.name self.name.as_str()
} }
fn color(&self) -> Color32 { fn color(&self) -> Color32 {
@ -236,7 +235,7 @@ impl PlotItem for HLine {
pub struct VLine { pub struct VLine {
pub(super) x: f64, pub(super) x: f64,
pub(super) stroke: Stroke, pub(super) stroke: Stroke,
pub(super) name: String, pub(super) name: Estring,
pub(super) highlight: bool, pub(super) highlight: bool,
pub(super) style: LineStyle, pub(super) style: LineStyle,
} }
@ -246,7 +245,7 @@ impl VLine {
Self { Self {
x: x.into(), x: x.into(),
stroke: Stroke::new(1.0, Color32::TRANSPARENT), stroke: Stroke::new(1.0, Color32::TRANSPARENT),
name: String::default(), name: Estring::default(),
highlight: false, highlight: false,
style: LineStyle::Solid, style: LineStyle::Solid,
} }
@ -288,9 +287,8 @@ impl VLine {
/// ///
/// Multiple plot items may share the same name, in which case they will also share an entry in /// Multiple plot items may share the same name, in which case they will also share an entry in
/// the legend. /// the legend.
#[allow(clippy::needless_pass_by_value)] pub fn name(mut self, name: impl Into<Estring>) -> Self {
pub fn name(mut self, name: impl ToString) -> Self { self.name = name.into();
self.name = name.to_string();
self self
} }
} }
@ -314,7 +312,7 @@ impl PlotItem for VLine {
fn initialize(&mut self, _x_range: RangeInclusive<f64>) {} fn initialize(&mut self, _x_range: RangeInclusive<f64>) {}
fn name(&self) -> &str { fn name(&self) -> &str {
&self.name self.name.as_str()
} }
fn color(&self) -> Color32 { fn color(&self) -> Color32 {
@ -542,7 +540,7 @@ impl MarkerShape {
pub struct Line { pub struct Line {
pub(super) series: Values, pub(super) series: Values,
pub(super) stroke: Stroke, pub(super) stroke: Stroke,
pub(super) name: String, pub(super) name: Estring,
pub(super) highlight: bool, pub(super) highlight: bool,
pub(super) fill: Option<f32>, pub(super) fill: Option<f32>,
pub(super) style: LineStyle, pub(super) style: LineStyle,
@ -602,9 +600,8 @@ impl Line {
/// ///
/// Multiple plot items may share the same name, in which case they will also share an entry in /// Multiple plot items may share the same name, in which case they will also share an entry in
/// the legend. /// the legend.
#[allow(clippy::needless_pass_by_value)] pub fn name(mut self, name: impl Into<Estring>) -> Self {
pub fn name(mut self, name: impl ToString) -> Self { self.name = name.into();
self.name = name.to_string();
self self
} }
} }
@ -709,7 +706,7 @@ impl PlotItem for Line {
pub struct Polygon { pub struct Polygon {
pub(super) series: Values, pub(super) series: Values,
pub(super) stroke: Stroke, pub(super) stroke: Stroke,
pub(super) name: String, pub(super) name: Estring,
pub(super) highlight: bool, pub(super) highlight: bool,
pub(super) fill_alpha: f32, pub(super) fill_alpha: f32,
pub(super) style: LineStyle, pub(super) style: LineStyle,
@ -770,9 +767,8 @@ impl Polygon {
/// ///
/// Multiple plot items may share the same name, in which case they will also share an entry in /// Multiple plot items may share the same name, in which case they will also share an entry in
/// the legend. /// the legend.
#[allow(clippy::needless_pass_by_value)] pub fn name(mut self, name: impl Into<Estring>) -> Self {
pub fn name(mut self, name: impl ToString) -> Self { self.name = name.into();
self.name = name.to_string();
self self
} }
} }
@ -842,20 +838,19 @@ impl PlotItem for Polygon {
/// Text inside the plot. /// Text inside the plot.
pub struct Text { pub struct Text {
pub(super) text: String, pub(super) text: Estring,
pub(super) style: TextStyle, pub(super) style: TextStyle,
pub(super) position: Value, pub(super) position: Value,
pub(super) name: String, pub(super) name: Estring,
pub(super) highlight: bool, pub(super) highlight: bool,
pub(super) color: Color32, pub(super) color: Color32,
pub(super) anchor: Align2, pub(super) anchor: Align2,
} }
impl Text { impl Text {
#[allow(clippy::needless_pass_by_value)] pub fn new(position: Value, text: impl Into<Estring>) -> Self {
pub fn new(position: Value, text: impl ToString) -> Self {
Self { Self {
text: text.to_string(), text: text.into(),
style: TextStyle::Small, style: TextStyle::Small,
position, position,
name: Default::default(), name: Default::default(),
@ -895,9 +890,8 @@ impl Text {
/// ///
/// Multiple plot items may share the same name, in which case they will also share an entry in /// Multiple plot items may share the same name, in which case they will also share an entry in
/// the legend. /// the legend.
#[allow(clippy::needless_pass_by_value)] pub fn name(mut self, name: impl Into<Estring>) -> Self {
pub fn name(mut self, name: impl ToString) -> Self { self.name = name.into();
self.name = name.to_string();
self self
} }
} }
@ -965,7 +959,7 @@ pub struct Points {
pub(super) filled: bool, pub(super) filled: bool,
/// The maximum extent of the marker from its center. /// The maximum extent of the marker from its center.
pub(super) radius: f32, pub(super) radius: f32,
pub(super) name: String, pub(super) name: Estring,
pub(super) highlight: bool, pub(super) highlight: bool,
pub(super) stems: Option<f32>, pub(super) stems: Option<f32>,
} }
@ -1026,9 +1020,8 @@ impl Points {
/// ///
/// Multiple plot items may share the same name, in which case they will also share an entry in /// Multiple plot items may share the same name, in which case they will also share an entry in
/// the legend. /// the legend.
#[allow(clippy::needless_pass_by_value)] pub fn name(mut self, name: impl Into<Estring>) -> Self {
pub fn name(mut self, name: impl ToString) -> Self { self.name = name.into();
self.name = name.to_string();
self self
} }
} }
@ -1221,7 +1214,7 @@ pub struct Arrows {
pub(super) origins: Values, pub(super) origins: Values,
pub(super) tips: Values, pub(super) tips: Values,
pub(super) color: Color32, pub(super) color: Color32,
pub(super) name: String, pub(super) name: Estring,
pub(super) highlight: bool, pub(super) highlight: bool,
} }
@ -1254,9 +1247,8 @@ impl Arrows {
/// ///
/// Multiple plot items may share the same name, in which case they will also share an entry in /// Multiple plot items may share the same name, in which case they will also share an entry in
/// the legend. /// the legend.
#[allow(clippy::needless_pass_by_value)] pub fn name(mut self, name: impl Into<Estring>) -> Self {
pub fn name(mut self, name: impl ToString) -> Self { self.name = name.into();
self.name = name.to_string();
self self
} }
} }
@ -1340,7 +1332,7 @@ pub struct PlotImage {
pub(super) bg_fill: Color32, pub(super) bg_fill: Color32,
pub(super) tint: Color32, pub(super) tint: Color32,
pub(super) highlight: bool, pub(super) highlight: bool,
pub(super) name: String, pub(super) name: Estring,
} }
impl PlotImage { impl PlotImage {
@ -1388,9 +1380,8 @@ impl PlotImage {
/// ///
/// Multiple plot items may share the same name, in which case they will also share an entry in /// Multiple plot items may share the same name, in which case they will also share an entry in
/// the legend. /// the legend.
#[allow(clippy::needless_pass_by_value)] pub fn name(mut self, name: impl Into<Estring>) -> Self {
pub fn name(mut self, name: impl ToString) -> Self { self.name = name.into();
self.name = name.to_string();
self self
} }
} }

View file

@ -1,7 +1,7 @@
use crate::*; use crate::*;
enum ProgressBarText { enum ProgressBarText {
Custom(String), Custom(Estring),
Percentage, Percentage,
} }
@ -31,9 +31,8 @@ impl ProgressBar {
} }
/// A custom text to display on the progress bar. /// A custom text to display on the progress bar.
#[allow(clippy::needless_pass_by_value)] pub fn text(mut self, text: impl Into<Estring>) -> Self {
pub fn text(mut self, text: impl ToString) -> Self { self.text = Some(ProgressBarText::Custom(text.into()));
self.text = Some(ProgressBarText::Custom(text.to_string()));
self self
} }
@ -125,9 +124,9 @@ impl Widget for ProgressBar {
} }
if let Some(text_kind) = text { if let Some(text_kind) = text {
let text = match text_kind { let text: Estring = match text_kind {
ProgressBarText::Custom(string) => string, ProgressBarText::Custom(string) => string,
ProgressBarText::Percentage => format!("{}%", (progress * 100.0) as usize), ProgressBarText::Percentage => format!("{}%", (progress * 100.0) as usize).into(),
}; };
ui.painter().sub_region(outer_rect).text( ui.painter().sub_region(outer_rect).text(
outer_rect.left_center() + vec2(ui.spacing().item_spacing.x, 0.0), outer_rect.left_center() + vec2(ui.spacing().item_spacing.x, 0.0),

View file

@ -24,16 +24,15 @@ use crate::*;
#[derive(Debug)] #[derive(Debug)]
pub struct SelectableLabel { pub struct SelectableLabel {
selected: bool, selected: bool,
text: String, text: Estring,
text_style: Option<TextStyle>, text_style: Option<TextStyle>,
} }
impl SelectableLabel { impl SelectableLabel {
#[allow(clippy::needless_pass_by_value)] pub fn new(selected: bool, text: impl Into<Estring>) -> Self {
pub fn new(selected: bool, text: impl ToString) -> Self {
Self { Self {
selected, selected,
text: text.to_string(), text: text.into(),
text_style: None, text_style: None,
} }
} }

View file

@ -1,5 +1,3 @@
#![allow(clippy::needless_pass_by_value)] // False positives with `impl ToString`
use crate::{widgets::Label, *}; use crate::{widgets::Label, *};
use std::ops::RangeInclusive; use std::ops::RangeInclusive;
@ -58,9 +56,9 @@ pub struct Slider<'a> {
clamp_to_range: bool, clamp_to_range: bool,
smart_aim: bool, smart_aim: bool,
show_value: bool, show_value: bool,
prefix: String, prefix: Estring,
suffix: String, suffix: Estring,
text: String, text: Estring,
text_color: Option<Color32>, text_color: Option<Color32>,
min_decimals: usize, min_decimals: usize,
max_decimals: Option<usize>, max_decimals: Option<usize>,
@ -115,20 +113,20 @@ impl<'a> Slider<'a> {
} }
/// Show a prefix before the number, e.g. "x: " /// Show a prefix before the number, e.g. "x: "
pub fn prefix(mut self, prefix: impl ToString) -> Self { pub fn prefix(mut self, prefix: impl Into<Estring>) -> Self {
self.prefix = prefix.to_string(); self.prefix = prefix.into();
self self
} }
/// Add a suffix to the number, this can be e.g. a unit ("°" or " m") /// Add a suffix to the number, this can be e.g. a unit ("°" or " m")
pub fn suffix(mut self, suffix: impl ToString) -> Self { pub fn suffix(mut self, suffix: impl Into<Estring>) -> Self {
self.suffix = suffix.to_string(); self.suffix = suffix.into();
self self
} }
/// Show a text next to the slider (e.g. explaining what the slider controls). /// Show a text next to the slider (e.g. explaining what the slider controls).
pub fn text(mut self, text: impl ToString) -> Self { pub fn text(mut self, text: impl Into<Estring>) -> Self {
self.text = text.to_string(); self.text = text.into();
self self
} }

View file

@ -225,7 +225,7 @@ impl TextBuffer for String {
#[must_use = "You should put this widget in an ui with `ui.add(widget);`"] #[must_use = "You should put this widget in an ui with `ui.add(widget);`"]
pub struct TextEdit<'t, S: TextBuffer = String> { pub struct TextEdit<'t, S: TextBuffer = String> {
text: &'t mut S, text: &'t mut S,
hint_text: String, hint_text: Estring,
id: Option<Id>, id: Option<Id>,
id_source: Option<Id>, id_source: Option<Id>,
text_style: Option<TextStyle>, text_style: Option<TextStyle>,
@ -302,9 +302,8 @@ impl<'t, S: TextBuffer> TextEdit<'t, S> {
} }
/// Show a faint hint text when the text field is empty. /// Show a faint hint text when the text field is empty.
#[allow(clippy::needless_pass_by_value)] pub fn hint_text(mut self, hint_text: impl Into<Estring>) -> Self {
pub fn hint_text(mut self, hint_text: impl ToString) -> Self { self.hint_text = hint_text.into();
self.hint_text = hint_text.to_string();
self self
} }

View file

@ -57,7 +57,7 @@ pub fn criterion_benchmark(c: &mut Criterion) {
ui.label("the quick brown fox jumps over the lazy dog"); ui.label("the quick brown fox jumps over the lazy dog");
}) })
}); });
c.bench_function("label format!", |b| { c.bench_function("label String", |b| {
b.iter(|| { b.iter(|| {
ui.label("the quick brown fox jumps over the lazy dog".to_owned()); ui.label("the quick brown fox jumps over the lazy dog".to_owned());
}) })
@ -77,20 +77,16 @@ pub fn criterion_benchmark(c: &mut Criterion) {
b.iter(|| { b.iter(|| {
use egui::epaint::text::{layout, LayoutJob}; use egui::epaint::text::{layout, LayoutJob};
let job = LayoutJob::simple( let job =
LOREM_IPSUM_LONG.to_owned(), LayoutJob::simple(LOREM_IPSUM_LONG, egui::TextStyle::Body, color, wrap_width);
egui::TextStyle::Body,
color,
wrap_width,
);
layout(&fonts, job.into()) layout(&fonts, job.into())
}) })
}); });
c.bench_function("text_layout_cached", |b| { c.bench_function("text_layout_cached", |b| {
b.iter(|| fonts.layout(LOREM_IPSUM_LONG.to_owned(), text_style, color, wrap_width)) b.iter(|| fonts.layout(LOREM_IPSUM_LONG, text_style, color, wrap_width))
}); });
let galley = fonts.layout(LOREM_IPSUM_LONG.to_owned(), text_style, color, wrap_width); let galley = fonts.layout(LOREM_IPSUM_LONG, text_style, color, wrap_width);
let mut tessellator = egui::epaint::Tessellator::from_options(Default::default()); let mut tessellator = egui::epaint::Tessellator::from_options(Default::default());
let mut mesh = egui::epaint::Mesh::default(); let mut mesh = egui::epaint::Mesh::default();
c.bench_function("tessellate_text", |b| { c.bench_function("tessellate_text", |b| {

View file

@ -262,7 +262,7 @@ impl ColorTest {
&mut self, &mut self,
ui: &mut Ui, ui: &mut Ui,
tex_allocator: &mut Option<&mut dyn epi::TextureAllocator>, tex_allocator: &mut Option<&mut dyn epi::TextureAllocator>,
label: &str, label: &'static str,
bg_fill: Color32, bg_fill: Color32,
gradient: &Gradient, gradient: &Gradient,
) { ) {
@ -284,7 +284,13 @@ impl ColorTest {
} }
} }
fn vertex_gradient(&mut self, ui: &mut Ui, label: &str, bg_fill: Color32, gradient: &Gradient) { fn vertex_gradient(
&mut self,
ui: &mut Ui,
label: &'static str,
bg_fill: Color32,
gradient: &Gradient,
) {
if !self.vertex_gradients { if !self.vertex_gradients {
return; return;
} }

View file

@ -117,7 +117,7 @@ impl MemoizedSyntaxHighlighter {
.highlight(is_dark_mode, code, language) .highlight(is_dark_mode, code, language)
.unwrap_or_else(|| { .unwrap_or_else(|| {
LayoutJob::simple( LayoutJob::simple(
code.into(), code.to_owned(),
egui::TextStyle::Monospace, egui::TextStyle::Monospace,
if is_dark_mode { if is_dark_mode {
egui::Color32::LIGHT_GRAY egui::Color32::LIGHT_GRAY
@ -172,7 +172,7 @@ impl Highligher {
use egui::text::{LayoutSection, TextFormat}; use egui::text::{LayoutSection, TextFormat};
let mut job = LayoutJob { let mut job = LayoutJob {
text: text.into(), text: text.to_owned().into(),
..Default::default() ..Default::default()
}; };
@ -226,7 +226,7 @@ impl Highligher {
fn highlight(&self, is_dark_mode: bool, mut text: &str, _language: &str) -> Option<LayoutJob> { fn highlight(&self, is_dark_mode: bool, mut text: &str, _language: &str) -> Option<LayoutJob> {
// Extremely simple syntax highlighter for when we compile without syntect // Extremely simple syntax highlighter for when we compile without syntect
use egui::text::TextFormat; use egui::text::{LayoutJobBuilder, TextFormat};
use egui::Color32; use egui::Color32;
let monospace = egui::TextStyle::Monospace; let monospace = egui::TextStyle::Monospace;
@ -265,7 +265,7 @@ impl Highligher {
}, },
); );
let mut job = LayoutJob::default(); let mut job = LayoutJobBuilder::default();
while !text.is_empty() { while !text.is_empty() {
if text.starts_with("//") { if text.starts_with("//") {
@ -308,7 +308,7 @@ impl Highligher {
} }
} }
Some(job) Some(job.build())
} }
} }

View file

@ -90,10 +90,12 @@ impl super::View for FontBook {
for (&chr, name) in named_chars { for (&chr, name) in named_chars {
if filter.is_empty() || name.contains(filter) || *filter == chr.to_string() { if filter.is_empty() || name.contains(filter) || *filter == chr.to_string() {
let button = egui::Button::new(chr).text_style(text_style).frame(false); let button = egui::Button::new(chr.to_string())
.text_style(text_style)
.frame(false);
let tooltip_ui = |ui: &mut egui::Ui| { let tooltip_ui = |ui: &mut egui::Ui| {
ui.add(egui::Label::new(chr).text_style(text_style)); ui.add(egui::Label::new(chr.to_string()).text_style(text_style));
ui.label(format!("{}\nU+{:X}\n\nClick to copy", name, chr as u32)); ui.label(format!("{}\nU+{:X}\n\nClick to copy", name, chr as u32));
}; };

View file

@ -329,7 +329,7 @@ impl Tree {
) )
} }
pub fn ui(&mut self, ui: &mut Ui) -> Action { pub fn ui(&mut self, ui: &mut Ui) -> Action {
self.1.ui(ui, 0, "root", &mut self.0) self.1.ui(ui, 0, "root".to_owned(), &mut self.0)
} }
} }
@ -342,16 +342,16 @@ impl SubTree {
&mut self, &mut self,
ui: &mut Ui, ui: &mut Ui,
depth: usize, depth: usize,
name: &str, name: String,
selected_name: &mut String, selected_name: &mut String,
) -> Action { ) -> Action {
let response = CollapsingHeader::new(name) let response = CollapsingHeader::new(name.clone())
.default_open(depth < 1) .default_open(depth < 1)
.selectable(true) .selectable(true)
.selected(selected_name.as_str() == name) .selected(selected_name.as_str() == name)
.show(ui, |ui| self.children_ui(ui, name, depth, selected_name)); .show(ui, |ui| self.children_ui(ui, &name, depth, selected_name));
if response.header_response.clicked() { if response.header_response.clicked() {
*selected_name = name.to_string(); *selected_name = name;
} }
response.body_returned.unwrap_or(Action::Keep) response.body_returned.unwrap_or(Action::Keep)
} }
@ -379,7 +379,7 @@ impl SubTree {
if tree.ui( if tree.ui(
ui, ui,
depth + 1, depth + 1,
&format!("{}/{}", parent_name, i), format!("{}/{}", parent_name, i),
selected_name, selected_name,
) == Action::Keep ) == Action::Keep
{ {
@ -401,9 +401,9 @@ impl SubTree {
// ---------------------------------------------------------------------------- // ----------------------------------------------------------------------------
fn text_layout_ui(ui: &mut egui::Ui) { fn text_layout_ui(ui: &mut egui::Ui) {
use egui::epaint::text::{LayoutJob, TextFormat}; use egui::epaint::text::{LayoutJobBuilder, TextFormat};
let mut job = LayoutJob::default(); let mut job = LayoutJobBuilder::default();
let first_row_indentation = 10.0; let first_row_indentation = 10.0;
@ -565,6 +565,7 @@ fn text_layout_ui(ui: &mut egui::Ui) {
}, },
); );
let mut job = job.build();
job.wrap_width = ui.available_width(); job.wrap_width = ui.available_width();
let galley = ui.fonts().layout_job(job); let galley = ui.fonts().layout_job(job);

View file

@ -241,7 +241,7 @@ fn example_plot() -> egui::plot::Plot {
.data_aspect(1.0) .data_aspect(1.0)
} }
fn doc_link_label<'a>(title: &'a str, search_term: &'a str) -> impl egui::Widget + 'a { fn doc_link_label(title: &'static str, search_term: &'static str) -> impl egui::Widget {
let label = format!("{}:", title); let label = format!("{}:", title);
let url = format!("https://docs.rs/egui?search={}", search_term); let url = format!("https://docs.rs/egui?search={}", search_term);
move |ui: &mut egui::Ui| { move |ui: &mut egui::Ui| {

View file

@ -132,10 +132,12 @@ impl epi::App for HttpApp {
} }
Err(error) => { Err(error) => {
// This should only happen if the fetch API isn't available or something similar. // This should only happen if the fetch API isn't available or something similar.
ui.add( let error = if error.is_empty() {
egui::Label::new(if error.is_empty() { "Error" } else { error }) "Error".to_owned()
.text_color(egui::Color32::RED), } else {
); error.clone()
};
ui.add(egui::Label::new(error).text_color(egui::Color32::RED));
} }
} }
} }
@ -325,7 +327,7 @@ impl ColoredText {
use egui::text::{LayoutJob, LayoutSection, TextFormat}; use egui::text::{LayoutJob, LayoutSection, TextFormat};
let mut job = LayoutJob { let mut job = LayoutJob {
text: text.into(), text: text.to_owned().into(),
..Default::default() ..Default::default()
}; };

View file

@ -43,7 +43,7 @@ pub fn item_ui(ui: &mut Ui, item: easy_mark::Item<'_>) {
} }
easy_mark::Item::Hyperlink(style, text, url) => { easy_mark::Item::Hyperlink(style, text, url) => {
let label = label_from_style(text, &style); let label = label_from_style(text, &style);
ui.add(Hyperlink::from_label_and_url(label, url)); ui.add(Hyperlink::from_label_and_url(label, url.to_owned()));
} }
easy_mark::Item::Separator => { easy_mark::Item::Separator => {
@ -75,7 +75,7 @@ pub fn item_ui(ui: &mut Ui, item: easy_mark::Item<'_>) {
} }
easy_mark::Item::CodeBlock(_language, code) => { easy_mark::Item::CodeBlock(_language, code) => {
let where_to_put_background = ui.painter().add(Shape::Noop); let where_to_put_background = ui.painter().add(Shape::Noop);
let mut rect = ui.monospace(code).rect; let mut rect = ui.monospace(code.to_owned()).rect;
rect = rect.expand(1.0); // looks better rect = rect.expand(1.0); // looks better
rect.max.x = ui.max_rect().max.x; rect.max.x = ui.max_rect().max.x;
let code_bg_color = ui.visuals().code_bg_color; let code_bg_color = ui.visuals().code_bg_color;
@ -102,7 +102,7 @@ fn label_from_style(text: &str, style: &easy_mark::Style) -> Label {
let small = small || raised; // Raised text is also smaller let small = small || raised; // Raised text is also smaller
let mut label = Label::new(text); let mut label = Label::new(text.to_owned());
if heading && !small { if heading && !small {
label = label.heading().strong(); label = label.heading().strong();
} }

View file

@ -121,7 +121,7 @@ impl WrapApp {
for (anchor, app) in self.apps.iter_mut() { for (anchor, app) in self.apps.iter_mut() {
if ui if ui
.selectable_label(self.selected_anchor == anchor, app.name()) .selectable_label(self.selected_anchor == anchor, app.name().to_owned())
.clicked() .clicked()
{ {
self.selected_anchor = anchor.to_owned(); self.selected_anchor = anchor.to_owned();

View file

@ -1,5 +1,5 @@
use crate::{ use crate::{
text::{Fonts, Galley, TextStyle}, text::{Estring, Fonts, Galley, TextStyle},
Color32, Mesh, Stroke, Color32, Mesh, Stroke,
}; };
use emath::*; use emath::*;
@ -166,16 +166,15 @@ impl Shape {
} }
} }
#[allow(clippy::needless_pass_by_value)]
pub fn text( pub fn text(
fonts: &Fonts, fonts: &Fonts,
pos: Pos2, pos: Pos2,
anchor: Align2, anchor: Align2,
text: impl ToString, text: impl Into<Estring>,
text_style: TextStyle, text_style: TextStyle,
color: Color32, color: Color32,
) -> Self { ) -> 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)); let rect = anchor.anchor_rect(Rect::from_min_size(pos, galley.size));
Self::galley(rect.min, galley) 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, mutex::Mutex,
text::{ text::{
font::{Font, FontImpl}, font::{Font, FontImpl},
Galley, LayoutJob, Estring, Galley, LayoutJob,
}, },
Texture, TextureAtlas, Texture, TextureAtlas,
}; };
@ -330,7 +330,7 @@ impl Fonts {
/// The implementation uses memoization so repeated calls are cheap. /// The implementation uses memoization so repeated calls are cheap.
pub fn layout( pub fn layout(
&self, &self,
text: String, text: impl Into<Estring>,
text_style: TextStyle, text_style: TextStyle,
color: crate::Color32, color: crate::Color32,
wrap_width: f32, wrap_width: f32,
@ -344,7 +344,7 @@ impl Fonts {
/// The implementation uses memoization so repeated calls are cheap. /// The implementation uses memoization so repeated calls are cheap.
pub fn layout_no_wrap( pub fn layout_no_wrap(
&self, &self,
text: String, text: impl Into<Estring>,
text_style: TextStyle, text_style: TextStyle,
color: crate::Color32, color: crate::Color32,
) -> Arc<Galley> { ) -> Arc<Galley> {
@ -357,7 +357,7 @@ impl Fonts {
/// The implementation uses memoization so repeated calls are cheap. /// The implementation uses memoization so repeated calls are cheap.
pub fn layout_delayed_color( pub fn layout_delayed_color(
&self, &self,
text: String, text: impl Into<Estring>,
text_style: TextStyle, text_style: TextStyle,
wrap_width: f32, wrap_width: f32,
) -> Arc<Galley> { ) -> Arc<Galley> {

View file

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

View file

@ -1,7 +1,7 @@
use std::ops::Range; use std::ops::Range;
use std::sync::Arc; use std::sync::Arc;
use super::{cursor::*, font::UvRect}; use super::{cursor::*, font::UvRect, Estring};
use crate::{Color32, Mesh, Stroke, TextStyle}; use crate::{Color32, Mesh, Stroke, TextStyle};
use emath::*; use emath::*;
@ -13,7 +13,7 @@ use emath::*;
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub struct LayoutJob { pub struct LayoutJob {
/// The complete text of this job, referenced by `LayoutSection`. /// 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. /// The different section, which can have different fonts, colors, etc.
pub sections: Vec<LayoutSection>, pub sections: Vec<LayoutSection>,
@ -53,7 +53,13 @@ impl Default for LayoutJob {
impl LayoutJob { impl LayoutJob {
/// Break on `\n` and at the given wrap width. /// Break on `\n` and at the given wrap width.
#[inline] #[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 { Self {
sections: vec![LayoutSection { sections: vec![LayoutSection {
leading_space: 0.0, leading_space: 0.0,
@ -69,7 +75,12 @@ impl LayoutJob {
/// Does not break on `\n`, but shows the replacement character instead. /// Does not break on `\n`, but shows the replacement character instead.
#[inline] #[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 { Self {
sections: vec![LayoutSection { sections: vec![LayoutSection {
leading_space: 0.0, leading_space: 0.0,
@ -87,18 +98,6 @@ impl LayoutJob {
pub fn is_empty(&self) -> bool { pub fn is_empty(&self) -> bool {
self.sections.is_empty() 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 { 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)] #[derive(Clone, Debug)]
pub struct LayoutSection { pub struct LayoutSection {
/// Can be used for first row indentation. /// Can be used for first row indentation.
@ -366,7 +396,7 @@ impl Galley {
#[inline(always)] #[inline(always)]
pub fn text(&self) -> &str { pub fn text(&self) -> &str {
&self.job.text self.job.text.as_str()
} }
} }