parent
9378cd5c6e
commit
09b8269326
30 changed files with 1121 additions and 712 deletions
|
@ -10,9 +10,11 @@ NOTE: [`epaint`](epaint/CHANGELOG.md), [`eframe`](eframe/CHANGELOG.md), [`egui_w
|
||||||
### Added ⭐
|
### Added ⭐
|
||||||
* Add context menus: See `Ui::menu_button` and `Response::context_menu` ([#543](https://github.com/emilk/egui/pull/543)).
|
* Add context menus: See `Ui::menu_button` and `Response::context_menu` ([#543](https://github.com/emilk/egui/pull/543)).
|
||||||
* You can now read and write the cursor of a `TextEdit` ([#848](https://github.com/emilk/egui/pull/848)).
|
* You can now read and write the cursor of a `TextEdit` ([#848](https://github.com/emilk/egui/pull/848)).
|
||||||
|
* Most widgets containing text (`Label`, `Button` etc) now supports rich text ([#855](https://github.com/emilk/egui/pull/855)).
|
||||||
|
|
||||||
### Changed 🔧
|
### Changed 🔧
|
||||||
* Unifiy the four `Memory` data buckets (`data`, `data_temp`, `id_data` and `id_data_temp`) into a single `Memory::data`, with a new interface ([#836](https://github.com/emilk/egui/pull/836)).
|
* Unifiy the four `Memory` data buckets (`data`, `data_temp`, `id_data` and `id_data_temp`) into a single `Memory::data`, with a new interface ([#836](https://github.com/emilk/egui/pull/836)).
|
||||||
|
* `ui.add(Button::new("…").text_color(…))` is now `ui.button(RichText::new("…").color(…))` (same for `Label` )([#855](https://github.com/emilk/egui/pull/855)).
|
||||||
|
|
||||||
### Contributors 🙏
|
### Contributors 🙏
|
||||||
* [mankinskin](https://github.com/mankinskin) ([#543](https://github.com/emilk/egui/pull/543))
|
* [mankinskin](https://github.com/mankinskin) ([#543](https://github.com/emilk/egui/pull/543))
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
use std::hash::Hash;
|
use std::hash::Hash;
|
||||||
|
|
||||||
use crate::{widgets::Label, *};
|
use crate::*;
|
||||||
use epaint::{Shape, TextStyle};
|
use epaint::{Shape, TextStyle};
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug)]
|
#[derive(Clone, Copy, Debug)]
|
||||||
|
@ -141,7 +141,7 @@ pub(crate) fn paint_icon(ui: &mut Ui, openness: f32, response: &Response) {
|
||||||
/// ```
|
/// ```
|
||||||
#[must_use = "You should call .show()"]
|
#[must_use = "You should call .show()"]
|
||||||
pub struct CollapsingHeader {
|
pub struct CollapsingHeader {
|
||||||
label: Label,
|
text: WidgetText,
|
||||||
default_open: bool,
|
default_open: bool,
|
||||||
id_source: Id,
|
id_source: Id,
|
||||||
enabled: bool,
|
enabled: bool,
|
||||||
|
@ -157,11 +157,11 @@ 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(text: impl Into<WidgetText>) -> Self {
|
||||||
let label = Label::new(label).wrap(false);
|
let text = text.into();
|
||||||
let id_source = Id::new(label.text());
|
let id_source = Id::new(text.text());
|
||||||
Self {
|
Self {
|
||||||
label,
|
text,
|
||||||
default_open: false,
|
default_open: false,
|
||||||
id_source,
|
id_source,
|
||||||
enabled: true,
|
enabled: true,
|
||||||
|
@ -185,10 +185,9 @@ impl CollapsingHeader {
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
/// By default, the `CollapsingHeader` text style is `TextStyle::Button`.
|
#[deprecated = "Replaced by: CollapsingHeader::new(RichText::new(text).text_style(…))"]
|
||||||
/// Call `.text_style(style)` to change this.
|
|
||||||
pub fn text_style(mut self, text_style: TextStyle) -> Self {
|
pub fn text_style(mut self, text_style: TextStyle) -> Self {
|
||||||
self.label = self.label.text_style(text_style);
|
self.text = self.text.text_style(text_style);
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -252,7 +251,7 @@ impl CollapsingHeader {
|
||||||
"Horizontal collapsing is unimplemented"
|
"Horizontal collapsing is unimplemented"
|
||||||
);
|
);
|
||||||
let Self {
|
let Self {
|
||||||
mut label,
|
text,
|
||||||
default_open,
|
default_open,
|
||||||
id_source,
|
id_source,
|
||||||
enabled: _,
|
enabled: _,
|
||||||
|
@ -261,11 +260,6 @@ impl CollapsingHeader {
|
||||||
show_background: _,
|
show_background: _,
|
||||||
} = self;
|
} = self;
|
||||||
|
|
||||||
label.text_style = label
|
|
||||||
.text_style
|
|
||||||
.or(ui.style().override_text_style)
|
|
||||||
.or(Some(TextStyle::Button));
|
|
||||||
|
|
||||||
// TODO: horizontal layout, with icon and text as labels. Insert background behind using Frame.
|
// TODO: horizontal layout, with icon and text as labels. Insert background behind using Frame.
|
||||||
|
|
||||||
let id = ui.make_persistent_id(id_source);
|
let id = ui.make_persistent_id(id_source);
|
||||||
|
@ -273,23 +267,24 @@ impl CollapsingHeader {
|
||||||
|
|
||||||
let available = ui.available_rect_before_wrap();
|
let available = ui.available_rect_before_wrap();
|
||||||
let text_pos = available.min + vec2(ui.spacing().indent, 0.0);
|
let text_pos = available.min + vec2(ui.spacing().indent, 0.0);
|
||||||
let galley =
|
let wrap_width = available.right() - text_pos.x;
|
||||||
label.layout_width(ui, available.right() - text_pos.x, Color32::TEMPORARY_COLOR);
|
let wrap = Some(false);
|
||||||
let text_max_x = text_pos.x + galley.size().x;
|
let text = text.into_galley(ui, wrap, wrap_width, TextStyle::Button);
|
||||||
|
let text_max_x = text_pos.x + text.size().x;
|
||||||
|
|
||||||
let mut desired_width = text_max_x + button_padding.x - available.left();
|
let mut desired_width = text_max_x + button_padding.x - available.left();
|
||||||
if ui.visuals().collapsing_header_frame {
|
if ui.visuals().collapsing_header_frame {
|
||||||
desired_width = desired_width.max(available.width()); // fill full width
|
desired_width = desired_width.max(available.width()); // fill full width
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut desired_size = vec2(desired_width, galley.size().y + 2.0 * button_padding.y);
|
let mut desired_size = vec2(desired_width, text.size().y + 2.0 * button_padding.y);
|
||||||
desired_size = desired_size.at_least(ui.spacing().interact_size);
|
desired_size = desired_size.at_least(ui.spacing().interact_size);
|
||||||
let (_, rect) = ui.allocate_space(desired_size);
|
let (_, rect) = ui.allocate_space(desired_size);
|
||||||
|
|
||||||
let mut header_response = ui.interact(rect, id, Sense::click());
|
let mut header_response = ui.interact(rect, id, Sense::click());
|
||||||
let text_pos = pos2(
|
let text_pos = pos2(
|
||||||
text_pos.x,
|
text_pos.x,
|
||||||
header_response.rect.center().y - galley.size().y / 2.0,
|
header_response.rect.center().y - text.size().y / 2.0,
|
||||||
);
|
);
|
||||||
|
|
||||||
let mut state = State::from_memory_with_default_open(ui.ctx(), id, default_open);
|
let mut state = State::from_memory_with_default_open(ui.ctx(), id, default_open);
|
||||||
|
@ -298,16 +293,11 @@ impl CollapsingHeader {
|
||||||
header_response.mark_changed();
|
header_response.mark_changed();
|
||||||
}
|
}
|
||||||
header_response
|
header_response
|
||||||
.widget_info(|| WidgetInfo::labeled(WidgetType::CollapsingHeader, galley.text()));
|
.widget_info(|| WidgetInfo::labeled(WidgetType::CollapsingHeader, text.text()));
|
||||||
|
|
||||||
let visuals = ui
|
let visuals = ui
|
||||||
.style()
|
.style()
|
||||||
.interact_selectable(&header_response, self.selected);
|
.interact_selectable(&header_response, self.selected);
|
||||||
let text_color = ui
|
|
||||||
.style()
|
|
||||||
.visuals
|
|
||||||
.override_text_color
|
|
||||||
.unwrap_or_else(|| visuals.text_color());
|
|
||||||
|
|
||||||
if ui.visuals().collapsing_header_frame || self.show_background {
|
if ui.visuals().collapsing_header_frame || self.show_background {
|
||||||
ui.painter().add(epaint::RectShape {
|
ui.painter().add(epaint::RectShape {
|
||||||
|
@ -343,7 +333,7 @@ impl CollapsingHeader {
|
||||||
paint_icon(ui, openness, &icon_response);
|
paint_icon(ui, openness, &icon_response);
|
||||||
}
|
}
|
||||||
|
|
||||||
ui.painter().galley_with_color(text_pos, galley, text_color);
|
text.paint_with_visuals(ui.painter(), text_pos, &visuals);
|
||||||
|
|
||||||
Prepared {
|
Prepared {
|
||||||
id,
|
id,
|
||||||
|
|
|
@ -1,8 +1,6 @@
|
||||||
use crate::{style::WidgetVisuals, *};
|
use crate::{style::WidgetVisuals, *};
|
||||||
use epaint::Shape;
|
use epaint::Shape;
|
||||||
|
|
||||||
// TODO: this should be builder struct so we can set options like width.
|
|
||||||
|
|
||||||
/// A drop-down selection menu with a descriptive label.
|
/// A drop-down selection menu with a descriptive label.
|
||||||
///
|
///
|
||||||
/// ```
|
/// ```
|
||||||
|
@ -10,7 +8,7 @@ use epaint::Shape;
|
||||||
/// # enum Enum { First, Second, Third }
|
/// # enum Enum { First, Second, Third }
|
||||||
/// # let mut selected = Enum::First;
|
/// # let mut selected = Enum::First;
|
||||||
/// # let mut ui = &mut egui::Ui::__test();
|
/// # let mut ui = &mut egui::Ui::__test();
|
||||||
/// egui::ComboBox::from_label( "Select one!")
|
/// egui::ComboBox::from_label("Select one!")
|
||||||
/// .selected_text(format!("{:?}", selected))
|
/// .selected_text(format!("{:?}", selected))
|
||||||
/// .show_ui(ui, |ui| {
|
/// .show_ui(ui, |ui| {
|
||||||
/// ui.selectable_value(&mut selected, Enum::First, "First");
|
/// ui.selectable_value(&mut selected, Enum::First, "First");
|
||||||
|
@ -22,14 +20,14 @@ use epaint::Shape;
|
||||||
#[must_use = "You should call .show*"]
|
#[must_use = "You should call .show*"]
|
||||||
pub struct ComboBox {
|
pub struct ComboBox {
|
||||||
id_source: Id,
|
id_source: Id,
|
||||||
label: Option<Label>,
|
label: Option<WidgetText>,
|
||||||
selected_text: String,
|
selected_text: WidgetText,
|
||||||
width: Option<f32>,
|
width: Option<f32>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ComboBox {
|
impl ComboBox {
|
||||||
/// Label shown next to the combo box
|
/// Label shown next to the combo box
|
||||||
pub fn from_label(label: impl Into<Label>) -> Self {
|
pub fn from_label(label: impl Into<WidgetText>) -> Self {
|
||||||
let label = label.into();
|
let label = label.into();
|
||||||
Self {
|
Self {
|
||||||
id_source: Id::new(label.text()),
|
id_source: Id::new(label.text()),
|
||||||
|
@ -56,9 +54,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<WidgetText>) -> 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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -95,7 +92,7 @@ impl ComboBox {
|
||||||
if let Some(label) = label {
|
if let Some(label) = label {
|
||||||
ir.response
|
ir.response
|
||||||
.widget_info(|| WidgetInfo::labeled(WidgetType::ComboBox, label.text()));
|
.widget_info(|| WidgetInfo::labeled(WidgetType::ComboBox, label.text()));
|
||||||
ir.response |= ui.add(label);
|
ir.response |= ui.label(label);
|
||||||
} else {
|
} else {
|
||||||
ir.response
|
ir.response
|
||||||
.widget_info(|| WidgetInfo::labeled(WidgetType::ComboBox, ""));
|
.widget_info(|| WidgetInfo::labeled(WidgetType::ComboBox, ""));
|
||||||
|
@ -115,7 +112,7 @@ impl ComboBox {
|
||||||
/// # let mut ui = &mut egui::Ui::__test();
|
/// # let mut ui = &mut egui::Ui::__test();
|
||||||
/// let alternatives = ["a", "b", "c", "d"];
|
/// let alternatives = ["a", "b", "c", "d"];
|
||||||
/// let mut selected = 2;
|
/// let mut selected = 2;
|
||||||
/// egui::ComboBox::from_label( "Select one!").show_index(
|
/// egui::ComboBox::from_label("Select one!").show_index(
|
||||||
/// ui,
|
/// ui,
|
||||||
/// &mut selected,
|
/// &mut selected,
|
||||||
/// alternatives.len(),
|
/// alternatives.len(),
|
||||||
|
@ -151,11 +148,10 @@ impl ComboBox {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(clippy::needless_pass_by_value)]
|
|
||||||
fn combo_box_dyn<'c, R>(
|
fn combo_box_dyn<'c, R>(
|
||||||
ui: &mut Ui,
|
ui: &mut Ui,
|
||||||
button_id: Id,
|
button_id: Id,
|
||||||
selected: impl ToString,
|
selected_text: WidgetText,
|
||||||
menu_contents: Box<dyn FnOnce(&mut Ui) -> R + 'c>,
|
menu_contents: Box<dyn FnOnce(&mut Ui) -> R + 'c>,
|
||||||
) -> InnerResponse<Option<R>> {
|
) -> InnerResponse<Option<R>> {
|
||||||
let popup_id = button_id.with("popup");
|
let popup_id = button_id.with("popup");
|
||||||
|
@ -166,9 +162,7 @@ fn combo_box_dyn<'c, 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 = selected_text.into_galley(ui, Some(false), f32::INFINITY, TextStyle::Button);
|
||||||
ui.fonts()
|
|
||||||
.layout_delayed_color(selected.to_string(), 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);
|
||||||
|
@ -188,8 +182,7 @@ fn combo_box_dyn<'c, R>(
|
||||||
paint_icon(ui.painter(), icon_rect.expand(visuals.expansion), visuals);
|
paint_icon(ui.painter(), icon_rect.expand(visuals.expansion), visuals);
|
||||||
|
|
||||||
let text_rect = Align2::LEFT_CENTER.align_size_within_rect(galley.size(), rect);
|
let text_rect = Align2::LEFT_CENTER.align_size_within_rect(galley.size(), rect);
|
||||||
ui.painter()
|
galley.paint_with_visuals(ui.painter(), text_rect.min, visuals);
|
||||||
.galley_with_color(text_rect.min, galley, visuals.text_color());
|
|
||||||
});
|
});
|
||||||
|
|
||||||
if button_response.clicked() {
|
if button_response.clicked() {
|
||||||
|
|
|
@ -226,9 +226,9 @@ fn show_tooltip_at_avoid_dyn<'c, 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<WidgetText>) -> Option<()> {
|
||||||
show_tooltip(ctx, id, |ui| {
|
show_tooltip(ctx, id, |ui| {
|
||||||
ui.add(crate::widgets::Label::new(text));
|
crate::widgets::Label::new(text).ui(ui);
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
// WARNING: the code in here is horrible. It is a behemoth that needs breaking up into simpler parts.
|
// WARNING: the code in here is horrible. It is a behemoth that needs breaking up into simpler parts.
|
||||||
|
|
||||||
use crate::{widgets::*, *};
|
use crate::{widget_text::WidgetTextGalley, *};
|
||||||
use epaint::*;
|
use epaint::*;
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
|
@ -23,7 +23,7 @@ use super::*;
|
||||||
/// });
|
/// });
|
||||||
#[must_use = "You should call .show()"]
|
#[must_use = "You should call .show()"]
|
||||||
pub struct Window<'open> {
|
pub struct Window<'open> {
|
||||||
title_label: Label,
|
title: WidgetText,
|
||||||
open: Option<&'open mut bool>,
|
open: Option<&'open mut bool>,
|
||||||
area: Area,
|
area: Area,
|
||||||
frame: Option<Frame>,
|
frame: Option<Frame>,
|
||||||
|
@ -37,13 +37,11 @@ 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<WidgetText>) -> Self {
|
||||||
pub fn new(title: impl ToString) -> Self {
|
let title = title.into().fallback_text_style(TextStyle::Heading);
|
||||||
let title = title.to_string();
|
let area = Area::new(title.text());
|
||||||
let area = Area::new(&title);
|
|
||||||
let title_label = Label::new(title).text_style(TextStyle::Heading).wrap(false);
|
|
||||||
Self {
|
Self {
|
||||||
title_label,
|
title,
|
||||||
open: None,
|
open: None,
|
||||||
area,
|
area,
|
||||||
frame: None,
|
frame: None,
|
||||||
|
@ -250,7 +248,7 @@ impl<'open> Window<'open> {
|
||||||
add_contents: Box<dyn FnOnce(&mut Ui) -> R + 'c>,
|
add_contents: Box<dyn FnOnce(&mut Ui) -> R + 'c>,
|
||||||
) -> Option<InnerResponse<Option<R>>> {
|
) -> Option<InnerResponse<Option<R>>> {
|
||||||
let Window {
|
let Window {
|
||||||
title_label,
|
title,
|
||||||
open,
|
open,
|
||||||
area,
|
area,
|
||||||
frame,
|
frame,
|
||||||
|
@ -299,7 +297,7 @@ impl<'open> Window<'open> {
|
||||||
.and_then(|window_interaction| {
|
.and_then(|window_interaction| {
|
||||||
// Calculate roughly how much larger the window size is compared to the inner rect
|
// Calculate roughly how much larger the window size is compared to the inner rect
|
||||||
let title_bar_height = if with_title_bar {
|
let title_bar_height = if with_title_bar {
|
||||||
title_label.font_height(ctx.fonts(), &ctx.style()) + title_content_spacing
|
title.font_height(ctx.fonts(), &ctx.style()) + title_content_spacing
|
||||||
} else {
|
} else {
|
||||||
0.0
|
0.0
|
||||||
};
|
};
|
||||||
|
@ -336,7 +334,7 @@ impl<'open> Window<'open> {
|
||||||
let title_bar = if with_title_bar {
|
let title_bar = if with_title_bar {
|
||||||
let title_bar = show_title_bar(
|
let title_bar = show_title_bar(
|
||||||
&mut frame.content_ui,
|
&mut frame.content_ui,
|
||||||
title_label,
|
title,
|
||||||
show_close_button,
|
show_close_button,
|
||||||
collapsing_id,
|
collapsing_id,
|
||||||
&mut collapsing,
|
&mut collapsing,
|
||||||
|
@ -745,22 +743,21 @@ fn paint_frame_interaction(
|
||||||
|
|
||||||
struct TitleBar {
|
struct TitleBar {
|
||||||
id: Id,
|
id: Id,
|
||||||
title_label: Label,
|
title_galley: WidgetTextGalley,
|
||||||
title_galley: std::sync::Arc<Galley>,
|
|
||||||
min_rect: Rect,
|
min_rect: Rect,
|
||||||
rect: Rect,
|
rect: Rect,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn show_title_bar(
|
fn show_title_bar(
|
||||||
ui: &mut Ui,
|
ui: &mut Ui,
|
||||||
title_label: Label,
|
title: WidgetText,
|
||||||
show_close_button: bool,
|
show_close_button: bool,
|
||||||
collapsing_id: Id,
|
collapsing_id: Id,
|
||||||
collapsing: &mut collapsing_header::State,
|
collapsing: &mut collapsing_header::State,
|
||||||
collapsible: bool,
|
collapsible: bool,
|
||||||
) -> TitleBar {
|
) -> TitleBar {
|
||||||
let inner_response = ui.horizontal(|ui| {
|
let inner_response = ui.horizontal(|ui| {
|
||||||
let height = title_label
|
let height = title
|
||||||
.font_height(ui.fonts(), ui.style())
|
.font_height(ui.fonts(), ui.style())
|
||||||
.max(ui.spacing().interact_size.y);
|
.max(ui.spacing().interact_size.y);
|
||||||
ui.set_min_height(height);
|
ui.set_min_height(height);
|
||||||
|
@ -782,7 +779,7 @@ fn show_title_bar(
|
||||||
collapsing_header::paint_icon(ui, openness, &collapse_button_response);
|
collapsing_header::paint_icon(ui, openness, &collapse_button_response);
|
||||||
}
|
}
|
||||||
|
|
||||||
let title_galley = title_label.layout(ui);
|
let title_galley = title.into_galley(ui, Some(false), f32::INFINITY, TextStyle::Heading);
|
||||||
|
|
||||||
let minimum_width = if collapsible || show_close_button {
|
let minimum_width = if collapsible || show_close_button {
|
||||||
// If at least one button is shown we make room for both buttons (since title is centered):
|
// If at least one button is shown we make room for both buttons (since title is centered):
|
||||||
|
@ -795,7 +792,6 @@ fn show_title_bar(
|
||||||
|
|
||||||
TitleBar {
|
TitleBar {
|
||||||
id,
|
id,
|
||||||
title_label,
|
|
||||||
title_galley,
|
title_galley,
|
||||||
min_rect,
|
min_rect,
|
||||||
rect: Rect::NAN, // Will be filled in later
|
rect: Rect::NAN, // Will be filled in later
|
||||||
|
@ -830,20 +826,16 @@ impl TitleBar {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Always have inactive style for the window.
|
|
||||||
// It is VERY annoying to e.g. change it when moving the window.
|
|
||||||
let style = ui.visuals().widgets.inactive;
|
|
||||||
|
|
||||||
self.title_label = self.title_label.text_color(style.fg_stroke.color);
|
|
||||||
|
|
||||||
let full_top_rect = Rect::from_x_y_ranges(self.rect.x_range(), self.min_rect.y_range());
|
let full_top_rect = Rect::from_x_y_ranges(self.rect.x_range(), self.min_rect.y_range());
|
||||||
let text_pos =
|
let text_pos =
|
||||||
emath::align::center_size_in_rect(self.title_galley.size(), full_top_rect).left_top();
|
emath::align::center_size_in_rect(self.title_galley.size(), full_top_rect).left_top();
|
||||||
let text_pos = text_pos - self.title_galley.rect.min.to_vec2();
|
let text_pos = text_pos - self.title_galley.galley().rect.min.to_vec2();
|
||||||
let text_pos = text_pos - 1.5 * Vec2::Y; // HACK: center on x-height of text (looks better)
|
let text_pos = text_pos - 1.5 * Vec2::Y; // HACK: center on x-height of text (looks better)
|
||||||
let text_color = ui.visuals().text_color();
|
self.title_galley.paint_with_fallback_color(
|
||||||
self.title_label
|
ui.painter(),
|
||||||
.paint_galley(ui, text_pos, self.title_galley, false, text_color);
|
text_pos,
|
||||||
|
ui.visuals().text_color(),
|
||||||
|
);
|
||||||
|
|
||||||
if let Some(content_response) = &content_response {
|
if let Some(content_response) = &content_response {
|
||||||
// paint separator between title and content:
|
// paint separator between title and content:
|
||||||
|
|
|
@ -915,7 +915,7 @@ impl Context {
|
||||||
let text = format!("{} - {:?}", layer_id.short_debug_format(), area.rect(),);
|
let text = format!("{} - {:?}", layer_id.short_debug_format(), area.rect(),);
|
||||||
// TODO: `Sense::hover_highlight()`
|
// TODO: `Sense::hover_highlight()`
|
||||||
if ui
|
if ui
|
||||||
.add(Label::new(text).monospace().sense(Sense::click()))
|
.add(Label::new(RichText::new(text).monospace()).sense(Sense::click()))
|
||||||
.hovered
|
.hovered
|
||||||
&& is_visible
|
&& is_visible
|
||||||
{
|
{
|
||||||
|
|
|
@ -367,6 +367,7 @@ mod sense;
|
||||||
pub mod style;
|
pub mod style;
|
||||||
mod ui;
|
mod ui;
|
||||||
pub mod util;
|
pub mod util;
|
||||||
|
mod widget_text;
|
||||||
pub mod widgets;
|
pub mod widgets;
|
||||||
|
|
||||||
pub use epaint;
|
pub use epaint;
|
||||||
|
@ -408,6 +409,7 @@ pub use {
|
||||||
style::{Style, Visuals},
|
style::{Style, Visuals},
|
||||||
text::{Galley, TextFormat},
|
text::{Galley, TextFormat},
|
||||||
ui::Ui,
|
ui::Ui,
|
||||||
|
widget_text::{RichText, WidgetText},
|
||||||
widgets::*,
|
widgets::*,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -416,10 +418,10 @@ pub use {
|
||||||
/// Helper function that adds a label when compiling with debug assertions enabled.
|
/// Helper function that adds a label when compiling with debug assertions enabled.
|
||||||
pub fn warn_if_debug_build(ui: &mut crate::Ui) {
|
pub fn warn_if_debug_build(ui: &mut crate::Ui) {
|
||||||
if cfg!(debug_assertions) {
|
if cfg!(debug_assertions) {
|
||||||
ui.add(
|
ui.label(
|
||||||
crate::Label::new("‼ Debug build ‼")
|
RichText::new("‼ Debug build ‼")
|
||||||
.small()
|
.small()
|
||||||
.text_color(crate::Color32::RED),
|
.color(crate::Color32::RED),
|
||||||
)
|
)
|
||||||
.on_hover_text("egui was compiled with debug assertions enabled.");
|
.on_hover_text("egui was compiled with debug assertions enabled.");
|
||||||
}
|
}
|
||||||
|
@ -437,7 +439,7 @@ pub fn warn_if_debug_build(ui: &mut crate::Ui) {
|
||||||
macro_rules! github_link_file_line {
|
macro_rules! github_link_file_line {
|
||||||
($github_url: expr, $label: expr) => {{
|
($github_url: expr, $label: expr) => {{
|
||||||
let url = format!("{}{}#L{}", $github_url, file!(), line!());
|
let url = format!("{}{}#L{}", $github_url, file!(), line!());
|
||||||
$crate::Hyperlink::new(url).text($label)
|
$crate::Hyperlink::from_label_and_url($label, url)
|
||||||
}};
|
}};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -451,7 +453,7 @@ macro_rules! github_link_file_line {
|
||||||
macro_rules! github_link_file {
|
macro_rules! github_link_file {
|
||||||
($github_url: expr, $label: expr) => {{
|
($github_url: expr, $label: expr) => {{
|
||||||
let url = format!("{}{}", $github_url, file!());
|
let url = format!("{}{}", $github_url, file!());
|
||||||
$crate::Hyperlink::new(url).text($label)
|
$crate::Hyperlink::from_label_and_url($label, url)
|
||||||
}};
|
}};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -92,7 +92,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_button<R>(
|
pub fn menu_button<R>(
|
||||||
ui: &mut Ui,
|
ui: &mut Ui,
|
||||||
title: impl ToString,
|
title: impl Into<WidgetText>,
|
||||||
add_contents: impl FnOnce(&mut Ui) -> R,
|
add_contents: impl FnOnce(&mut Ui) -> R,
|
||||||
) -> InnerResponse<Option<R>> {
|
) -> InnerResponse<Option<R>> {
|
||||||
stationary_menu_impl(ui, title, Box::new(add_contents))
|
stationary_menu_impl(ui, title, Box::new(add_contents))
|
||||||
|
@ -103,18 +103,17 @@ pub fn menu_button<R>(
|
||||||
pub(crate) fn submenu_button<R>(
|
pub(crate) fn submenu_button<R>(
|
||||||
ui: &mut Ui,
|
ui: &mut Ui,
|
||||||
parent_state: Arc<RwLock<MenuState>>,
|
parent_state: Arc<RwLock<MenuState>>,
|
||||||
title: impl ToString,
|
title: impl Into<WidgetText>,
|
||||||
add_contents: impl FnOnce(&mut Ui) -> R,
|
add_contents: impl FnOnce(&mut Ui) -> R,
|
||||||
) -> InnerResponse<Option<R>> {
|
) -> InnerResponse<Option<R>> {
|
||||||
SubMenu::new(parent_state, title).show(ui, add_contents)
|
SubMenu::new(parent_state, title).show(ui, add_contents)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// wrapper for the contents of every menu.
|
/// wrapper for the contents of every menu.
|
||||||
#[allow(clippy::needless_pass_by_value)]
|
|
||||||
pub(crate) fn menu_ui<'c, R>(
|
pub(crate) fn menu_ui<'c, R>(
|
||||||
ctx: &CtxRef,
|
ctx: &CtxRef,
|
||||||
menu_id: impl std::hash::Hash,
|
menu_id: impl std::hash::Hash,
|
||||||
menu_state_arc: Arc<RwLock<MenuState>>,
|
menu_state_arc: &Arc<RwLock<MenuState>>,
|
||||||
mut style: Style,
|
mut style: Style,
|
||||||
add_contents: impl FnOnce(&mut Ui) -> R + 'c,
|
add_contents: impl FnOnce(&mut Ui) -> R + 'c,
|
||||||
) -> InnerResponse<R> {
|
) -> InnerResponse<R> {
|
||||||
|
@ -152,15 +151,14 @@ pub(crate) fn menu_ui<'c, R>(
|
||||||
}
|
}
|
||||||
|
|
||||||
/// build a top level menu with a button
|
/// build a top level menu with a button
|
||||||
#[allow(clippy::needless_pass_by_value)]
|
|
||||||
fn stationary_menu_impl<'c, R>(
|
fn stationary_menu_impl<'c, R>(
|
||||||
ui: &mut Ui,
|
ui: &mut Ui,
|
||||||
title: impl ToString,
|
title: impl Into<WidgetText>,
|
||||||
add_contents: Box<dyn FnOnce(&mut Ui) -> R + 'c>,
|
add_contents: Box<dyn FnOnce(&mut Ui) -> R + 'c>,
|
||||||
) -> InnerResponse<Option<R>> {
|
) -> InnerResponse<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.text());
|
||||||
|
|
||||||
let mut bar_state = BarState::load(ui.ctx(), bar_id);
|
let mut bar_state = BarState::load(ui.ctx(), bar_id);
|
||||||
|
|
||||||
|
@ -372,20 +370,20 @@ impl MenuResponse {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub struct SubMenuButton {
|
pub struct SubMenuButton {
|
||||||
text: String,
|
text: WidgetText,
|
||||||
icon: String,
|
icon: WidgetText,
|
||||||
index: usize,
|
index: usize,
|
||||||
}
|
}
|
||||||
impl SubMenuButton {
|
impl SubMenuButton {
|
||||||
/// The `icon` can be an emoji (e.g. `⏵` right arrow), shown right of the label
|
/// The `icon` can be an emoji (e.g. `⏵` right arrow), shown right of the label
|
||||||
#[allow(clippy::needless_pass_by_value)]
|
fn new(text: impl Into<WidgetText>, icon: impl Into<WidgetText>, index: usize) -> Self {
|
||||||
fn new(text: impl ToString, icon: impl ToString, index: usize) -> Self {
|
|
||||||
Self {
|
Self {
|
||||||
text: text.to_string(),
|
text: text.into(),
|
||||||
icon: icon.to_string(),
|
icon: icon.into(),
|
||||||
index,
|
index,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn visuals<'a>(
|
fn visuals<'a>(
|
||||||
ui: &'a Ui,
|
ui: &'a Ui,
|
||||||
response: &'_ Response,
|
response: &'_ Response,
|
||||||
|
@ -398,11 +396,12 @@ impl SubMenuButton {
|
||||||
ui.style().interact(response)
|
ui.style().interact(response)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#[allow(clippy::needless_pass_by_value)]
|
|
||||||
pub fn icon(mut self, icon: impl ToString) -> Self {
|
pub fn icon(mut self, icon: impl Into<WidgetText>) -> Self {
|
||||||
self.icon = icon.to_string();
|
self.icon = icon.into();
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn show(self, ui: &mut Ui, menu_state: &MenuState, sub_id: Id) -> Response {
|
pub(crate) fn show(self, ui: &mut Ui, menu_state: &MenuState, sub_id: Id) -> Response {
|
||||||
let SubMenuButton { text, icon, .. } = self;
|
let SubMenuButton { text, icon, .. } = self;
|
||||||
|
|
||||||
|
@ -412,14 +411,10 @@ impl SubMenuButton {
|
||||||
let button_padding = ui.spacing().button_padding;
|
let button_padding = ui.spacing().button_padding;
|
||||||
let total_extra = button_padding + button_padding;
|
let total_extra = button_padding + button_padding;
|
||||||
let text_available_width = ui.available_width() - total_extra.x;
|
let text_available_width = ui.available_width() - total_extra.x;
|
||||||
let text_galley = ui
|
let text_galley = text.into_galley(ui, Some(true), text_available_width, text_style);
|
||||||
.fonts()
|
|
||||||
.layout_delayed_color(text, text_style, text_available_width);
|
|
||||||
|
|
||||||
let icon_available_width = text_available_width - text_galley.size().x;
|
let icon_available_width = text_available_width - text_galley.size().x;
|
||||||
let icon_galley = ui
|
let icon_galley = icon.into_galley(ui, Some(true), icon_available_width, text_style);
|
||||||
.fonts()
|
|
||||||
.layout_delayed_color(icon, text_style, icon_available_width);
|
|
||||||
let text_and_icon_size = Vec2::new(
|
let text_and_icon_size = Vec2::new(
|
||||||
text_galley.size().x + icon_galley.size().x,
|
text_galley.size().x + icon_galley.size().x,
|
||||||
text_galley.size().y.max(icon_galley.size().y),
|
text_galley.size().y.max(icon_galley.size().y),
|
||||||
|
@ -447,10 +442,8 @@ impl SubMenuButton {
|
||||||
);
|
);
|
||||||
|
|
||||||
let text_color = visuals.text_color();
|
let text_color = visuals.text_color();
|
||||||
ui.painter()
|
text_galley.paint_with_fallback_color(ui.painter(), text_pos, text_color);
|
||||||
.galley_with_color(text_pos, text_galley, text_color);
|
icon_galley.paint_with_fallback_color(ui.painter(), icon_pos, text_color);
|
||||||
ui.painter()
|
|
||||||
.galley_with_color(icon_pos, icon_galley, text_color);
|
|
||||||
}
|
}
|
||||||
response
|
response
|
||||||
}
|
}
|
||||||
|
@ -460,14 +453,14 @@ pub struct SubMenu {
|
||||||
parent_state: Arc<RwLock<MenuState>>,
|
parent_state: Arc<RwLock<MenuState>>,
|
||||||
}
|
}
|
||||||
impl SubMenu {
|
impl SubMenu {
|
||||||
#[allow(clippy::needless_pass_by_value)]
|
fn new(parent_state: Arc<RwLock<MenuState>>, text: impl Into<WidgetText>) -> Self {
|
||||||
fn new(parent_state: Arc<RwLock<MenuState>>, text: impl ToString) -> Self {
|
|
||||||
let index = parent_state.write().next_entry_index();
|
let index = parent_state.write().next_entry_index();
|
||||||
Self {
|
Self {
|
||||||
button: SubMenuButton::new(text, "⏵", index),
|
button: SubMenuButton::new(text, "⏵", index),
|
||||||
parent_state,
|
parent_state,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn show<R>(
|
pub fn show<R>(
|
||||||
self,
|
self,
|
||||||
ui: &mut Ui,
|
ui: &mut Ui,
|
||||||
|
@ -522,8 +515,7 @@ impl MenuState {
|
||||||
},
|
},
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
let menu_state_arc = menu_state.clone();
|
crate::menu::menu_ui(ctx, id, menu_state, style, add_contents)
|
||||||
crate::menu::menu_ui(ctx, id, menu_state_arc, style, add_contents)
|
|
||||||
}
|
}
|
||||||
fn show_submenu<R>(
|
fn show_submenu<R>(
|
||||||
&mut self,
|
&mut self,
|
||||||
|
|
|
@ -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, Id, LayerId, PointerButton, Sense, Ui, WidgetText, NUM_POINTER_BUTTONS,
|
||||||
};
|
};
|
||||||
use crate::{CtxRef, Id, LayerId, Sense, Ui};
|
|
||||||
|
|
||||||
// ----------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
@ -389,14 +388,14 @@ impl Response {
|
||||||
///
|
///
|
||||||
/// 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.
|
||||||
#[doc(alias = "tooltip")]
|
#[doc(alias = "tooltip")]
|
||||||
pub fn on_hover_text(self, text: impl ToString) -> Self {
|
pub fn on_hover_text(self, text: impl Into<WidgetText>) -> 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<WidgetText>) -> 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));
|
||||||
})
|
})
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
#![allow(clippy::if_same_then_else)]
|
#![allow(clippy::if_same_then_else)]
|
||||||
|
|
||||||
use crate::{color::*, emath::*, Response};
|
use crate::{color::*, emath::*, Response, RichText, WidgetText};
|
||||||
use epaint::{Shadow, Stroke, TextStyle};
|
use epaint::{Shadow, Stroke, TextStyle};
|
||||||
|
|
||||||
/// Specifies the look and feel of egui.
|
/// Specifies the look and feel of egui.
|
||||||
|
@ -581,15 +581,8 @@ impl Style {
|
||||||
ui.label("Default body text style:");
|
ui.label("Default body text style:");
|
||||||
ui.horizontal(|ui| {
|
ui.horizontal(|ui| {
|
||||||
for &style in &[TextStyle::Body, TextStyle::Monospace] {
|
for &style in &[TextStyle::Body, TextStyle::Monospace] {
|
||||||
if ui
|
let text = crate::RichText::new(format!("{:?}", style)).text_style(style);
|
||||||
.add(
|
ui.radio_value(body_text_style, style, text);
|
||||||
RadioButton::new(*body_text_style == style, format!("{:?}", style))
|
|
||||||
.text_style(style),
|
|
||||||
)
|
|
||||||
.clicked()
|
|
||||||
{
|
|
||||||
*body_text_style = style;
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
ui.end_row();
|
ui.end_row();
|
||||||
|
@ -603,17 +596,8 @@ impl Style {
|
||||||
.show_ui(ui, |ui| {
|
.show_ui(ui, |ui| {
|
||||||
ui.selectable_value(override_text_style, None, "None");
|
ui.selectable_value(override_text_style, None, "None");
|
||||||
for style in TextStyle::all() {
|
for style in TextStyle::all() {
|
||||||
// ui.selectable_value(override_text_style, Some(style), format!("{:?}", style));
|
let text = crate::RichText::new(format!("{:?}", style)).text_style(style);
|
||||||
let selected = *override_text_style == Some(style);
|
ui.selectable_value(override_text_style, Some(style), text);
|
||||||
if ui
|
|
||||||
.add(
|
|
||||||
SelectableLabel::new(selected, format!("{:?}", style))
|
|
||||||
.text_style(style),
|
|
||||||
)
|
|
||||||
.clicked()
|
|
||||||
{
|
|
||||||
*override_text_style = Some(style);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
ui.end_row();
|
ui.end_row();
|
||||||
|
@ -879,7 +863,7 @@ impl Visuals {
|
||||||
&mut widgets.noninteractive.fg_stroke.color,
|
&mut widgets.noninteractive.fg_stroke.color,
|
||||||
"Text color",
|
"Text color",
|
||||||
);
|
);
|
||||||
ui_color(ui, code_bg_color, Label::new("Code background").code()).on_hover_ui(|ui| {
|
ui_color(ui, code_bg_color, RichText::new("Code background").code()).on_hover_ui(|ui| {
|
||||||
ui.horizontal(|ui| {
|
ui.horizontal(|ui| {
|
||||||
ui.spacing_mut().item_spacing.x = 0.0;
|
ui.spacing_mut().item_spacing.x = 0.0;
|
||||||
ui.label("For monospaced inlined text ");
|
ui.label("For monospaced inlined text ");
|
||||||
|
@ -949,10 +933,10 @@ fn slider_vec2<'a>(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn ui_color(ui: &mut Ui, srgba: &mut Color32, label: impl Into<Label>) -> Response {
|
fn ui_color(ui: &mut Ui, srgba: &mut Color32, label: impl Into<WidgetText>) -> Response {
|
||||||
ui.horizontal(|ui| {
|
ui.horizontal(|ui| {
|
||||||
ui.color_edit_button_srgba(srgba);
|
ui.color_edit_button_srgba(srgba);
|
||||||
ui.add(label.into());
|
ui.label(label);
|
||||||
})
|
})
|
||||||
.response
|
.response
|
||||||
}
|
}
|
||||||
|
|
|
@ -283,6 +283,7 @@ impl Ui {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Should text wrap in this `Ui`?
|
/// Should text wrap in this `Ui`?
|
||||||
|
///
|
||||||
/// This is determined first by [`Style::wrap`], and then by the layout of this `Ui`.
|
/// This is determined first by [`Style::wrap`], and then by the layout of this `Ui`.
|
||||||
pub fn wrap_text(&self) -> bool {
|
pub fn wrap_text(&self) -> bool {
|
||||||
if let Some(wrap) = self.style.wrap {
|
if let Some(wrap) = self.style.wrap {
|
||||||
|
@ -290,8 +291,8 @@ impl Ui {
|
||||||
} else if let Some(grid) = self.placer.grid() {
|
} else if let Some(grid) = self.placer.grid() {
|
||||||
grid.wrap_text()
|
grid.wrap_text()
|
||||||
} else {
|
} else {
|
||||||
// In vertical layouts we wrap text, but in horizontal we keep going.
|
let layout = self.layout();
|
||||||
self.layout().is_vertical()
|
layout.is_vertical() || layout.is_horizontal() && layout.main_wrap()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -990,50 +991,54 @@ impl Ui {
|
||||||
///
|
///
|
||||||
/// See also [`Label`].
|
/// See also [`Label`].
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub fn label(&mut self, text: impl ToString) -> Response {
|
pub fn label(&mut self, text: impl Into<WidgetText>) -> Response {
|
||||||
Label::new(text).ui(self)
|
Label::new(text).ui(self)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Show colored text.
|
/// Show colored text.
|
||||||
///
|
///
|
||||||
/// Shortcut for `add(Label::new(text).text_color(color))`
|
/// Shortcut for `ui.label(RichText::new(text).color(color))`
|
||||||
pub fn colored_label(&mut self, color: impl Into<Color32>, text: impl ToString) -> Response {
|
pub fn colored_label(
|
||||||
Label::new(text).text_color(color).ui(self)
|
&mut self,
|
||||||
|
color: impl Into<Color32>,
|
||||||
|
text: impl Into<RichText>,
|
||||||
|
) -> Response {
|
||||||
|
Label::new(text.into().color(color)).ui(self)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Show large text.
|
/// Show large text.
|
||||||
///
|
///
|
||||||
/// Shortcut for `add(Label::new(text).heading())`
|
/// Shortcut for `ui.label(RichText::new(text).heading())`
|
||||||
pub fn heading(&mut self, text: impl ToString) -> Response {
|
pub fn heading(&mut self, text: impl Into<RichText>) -> Response {
|
||||||
Label::new(text).heading().ui(self)
|
Label::new(text.into().heading()).ui(self)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Show monospace (fixed width) text.
|
/// Show monospace (fixed width) text.
|
||||||
///
|
///
|
||||||
/// Shortcut for `add(Label::new(text).monospace())`
|
/// Shortcut for `ui.label(RichText::new(text).monospace())`
|
||||||
pub fn monospace(&mut self, text: impl ToString) -> Response {
|
pub fn monospace(&mut self, text: impl Into<RichText>) -> Response {
|
||||||
Label::new(text).monospace().ui(self)
|
Label::new(text.into().monospace()).ui(self)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Show text as monospace with a gray background.
|
/// Show text as monospace with a gray background.
|
||||||
///
|
///
|
||||||
/// Shortcut for `add(Label::new(text).code())`
|
/// Shortcut for `ui.label(RichText::new(text).code())`
|
||||||
pub fn code(&mut self, text: impl ToString) -> Response {
|
pub fn code(&mut self, text: impl Into<RichText>) -> Response {
|
||||||
Label::new(text).code().ui(self)
|
Label::new(text.into().code()).ui(self)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Show small text.
|
/// Show small text.
|
||||||
///
|
///
|
||||||
/// Shortcut for `add(Label::new(text).small())`
|
/// Shortcut for `ui.label(RichText::new(text).small())`
|
||||||
pub fn small(&mut self, text: impl ToString) -> Response {
|
pub fn small(&mut self, text: impl Into<RichText>) -> Response {
|
||||||
Label::new(text).small().ui(self)
|
Label::new(text.into().small()).ui(self)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Show text that stand out a bit (e.g. slightly brighter).
|
/// Show text that stand out a bit (e.g. slightly brighter).
|
||||||
///
|
///
|
||||||
/// Shortcut for `add(Label::new(text).strong())`
|
/// Shortcut for `ui.label(RichText::new(text).strong())`
|
||||||
pub fn strong(&mut self, text: impl ToString) -> Response {
|
pub fn strong(&mut self, text: impl Into<RichText>) -> Response {
|
||||||
Label::new(text).strong().ui(self)
|
Label::new(text.into().strong()).ui(self)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Shortcut for `add(Hyperlink::new(url))`
|
/// Shortcut for `add(Hyperlink::new(url))`
|
||||||
|
@ -1051,8 +1056,8 @@ 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<WidgetText>, url: impl ToString) -> Response {
|
||||||
Hyperlink::new(url).text(label).ui(self)
|
Hyperlink::from_label_and_url(label, url).ui(self)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// No newlines (`\n`) allowed. Pressing enter key will result in the `TextEdit` losing focus (`response.lost_focus`).
|
/// No newlines (`\n`) allowed. Pressing enter key will result in the `TextEdit` losing focus (`response.lost_focus`).
|
||||||
|
@ -1089,9 +1094,21 @@ impl Ui {
|
||||||
/// Shortcut for `add(Button::new(text))`
|
/// Shortcut for `add(Button::new(text))`
|
||||||
///
|
///
|
||||||
/// See also [`Button`].
|
/// See also [`Button`].
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// # let ui = &mut egui::Ui::__test();
|
||||||
|
/// if ui.button("Click me!").clicked() {
|
||||||
|
/// // …
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// # use egui::{RichText, Color32};
|
||||||
|
/// if ui.button(RichText::new("delete").color(Color32::RED)).clicked() {
|
||||||
|
/// // …
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
#[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]
|
||||||
pub fn button(&mut self, text: impl ToString) -> Response {
|
pub fn button(&mut self, text: impl Into<WidgetText>) -> Response {
|
||||||
Button::new(text).ui(self)
|
Button::new(text).ui(self)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1101,19 +1118,21 @@ 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<WidgetText>) -> 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 {
|
#[inline]
|
||||||
|
pub fn checkbox(&mut self, checked: &mut bool, text: impl Into<WidgetText>) -> 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 {
|
#[inline]
|
||||||
|
pub fn radio(&mut self, selected: bool, text: impl Into<WidgetText>) -> Response {
|
||||||
RadioButton::new(selected, text).ui(self)
|
RadioButton::new(selected, text).ui(self)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1134,11 +1153,12 @@ impl Ui {
|
||||||
/// if ui.add(egui::RadioButton::new(my_enum == Enum::First, "First")).clicked() {
|
/// if ui.add(egui::RadioButton::new(my_enum == Enum::First, "First")).clicked() {
|
||||||
/// my_enum = Enum::First
|
/// my_enum = Enum::First
|
||||||
/// }
|
/// }
|
||||||
|
/// ```
|
||||||
pub fn radio_value<Value: PartialEq>(
|
pub fn radio_value<Value: PartialEq>(
|
||||||
&mut self,
|
&mut self,
|
||||||
current_value: &mut Value,
|
current_value: &mut Value,
|
||||||
selected_value: Value,
|
selected_value: Value,
|
||||||
text: impl ToString,
|
text: impl Into<WidgetText>,
|
||||||
) -> 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() {
|
||||||
|
@ -1152,7 +1172,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<WidgetText>) -> Response {
|
||||||
SelectableLabel::new(checked, text).ui(self)
|
SelectableLabel::new(checked, text).ui(self)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1166,7 +1186,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<WidgetText>,
|
||||||
) -> 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() {
|
||||||
|
@ -1373,7 +1393,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<WidgetText>,
|
||||||
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)
|
||||||
|
@ -1642,10 +1662,6 @@ impl Ui {
|
||||||
self.placer.is_grid()
|
self.placer.is_grid()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn grid(&self) -> Option<&grid::GridLayout> {
|
|
||||||
self.placer.grid()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Move to the next row in a grid layout or wrapping layout.
|
/// Move to the next row in a grid layout or wrapping layout.
|
||||||
/// Otherwise does nothing.
|
/// Otherwise does nothing.
|
||||||
pub fn end_row(&mut self) {
|
pub fn end_row(&mut self) {
|
||||||
|
@ -1749,7 +1765,7 @@ impl Ui {
|
||||||
/// ```
|
/// ```
|
||||||
pub fn menu_button<R>(
|
pub fn menu_button<R>(
|
||||||
&mut self,
|
&mut self,
|
||||||
title: impl ToString,
|
title: impl Into<WidgetText>,
|
||||||
add_contents: impl FnOnce(&mut Ui) -> R,
|
add_contents: impl FnOnce(&mut Ui) -> R,
|
||||||
) -> InnerResponse<Option<R>> {
|
) -> InnerResponse<Option<R>> {
|
||||||
if let Some(menu_state) = self.menu_state.clone() {
|
if let Some(menu_state) = self.menu_state.clone() {
|
||||||
|
|
635
egui/src/widget_text.rs
Normal file
635
egui/src/widget_text.rs
Normal file
|
@ -0,0 +1,635 @@
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
style::WidgetVisuals, text::LayoutJob, Align, Color32, Galley, Pos2, Style, TextStyle, Ui,
|
||||||
|
Visuals,
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Text and optional style choices for it.
|
||||||
|
///
|
||||||
|
/// The style choices (font, color) are applied to the entire text.
|
||||||
|
/// For more detailed control, use [`crate::text::LayoutJob`] instead.
|
||||||
|
#[derive(Default)]
|
||||||
|
pub struct RichText {
|
||||||
|
text: String,
|
||||||
|
text_style: Option<TextStyle>,
|
||||||
|
background_color: Color32,
|
||||||
|
text_color: Option<Color32>,
|
||||||
|
code: bool,
|
||||||
|
strong: bool,
|
||||||
|
weak: bool,
|
||||||
|
strikethrough: bool,
|
||||||
|
underline: bool,
|
||||||
|
italics: bool,
|
||||||
|
raised: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<&str> for RichText {
|
||||||
|
#[inline]
|
||||||
|
fn from(text: &str) -> Self {
|
||||||
|
RichText::new(text)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<&String> for RichText {
|
||||||
|
#[inline]
|
||||||
|
fn from(text: &String) -> Self {
|
||||||
|
RichText::new(text)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<String> for RichText {
|
||||||
|
#[inline]
|
||||||
|
fn from(text: String) -> Self {
|
||||||
|
RichText::new(text)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RichText {
|
||||||
|
#[inline]
|
||||||
|
pub fn new(text: impl Into<String>) -> Self {
|
||||||
|
Self {
|
||||||
|
text: text.into(),
|
||||||
|
..Default::default()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn is_empty(&self) -> bool {
|
||||||
|
self.text.is_empty()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn text(&self) -> &str {
|
||||||
|
&self.text
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Override the [`TextStyle`].
|
||||||
|
#[inline]
|
||||||
|
pub fn text_style(mut self, text_style: TextStyle) -> Self {
|
||||||
|
self.text_style = Some(text_style);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set the [`TextStyle`] unless it has already been set
|
||||||
|
#[inline]
|
||||||
|
pub fn fallback_text_style(mut self, text_style: TextStyle) -> Self {
|
||||||
|
self.text_style.get_or_insert(text_style);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Use [`TextStyle::Heading`].
|
||||||
|
#[inline]
|
||||||
|
pub fn heading(self) -> Self {
|
||||||
|
self.text_style(TextStyle::Heading)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Use [`TextStyle::Monospace`].
|
||||||
|
#[inline]
|
||||||
|
pub fn monospace(self) -> Self {
|
||||||
|
self.text_style(TextStyle::Monospace)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Monospace label with different background color.
|
||||||
|
#[inline]
|
||||||
|
pub fn code(mut self) -> Self {
|
||||||
|
self.code = true;
|
||||||
|
self.text_style(TextStyle::Monospace)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Extra strong text (stronger color).
|
||||||
|
#[inline]
|
||||||
|
pub fn strong(mut self) -> Self {
|
||||||
|
self.strong = true;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Extra weak text (fainter color).
|
||||||
|
#[inline]
|
||||||
|
pub fn weak(mut self) -> Self {
|
||||||
|
self.weak = true;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Draw a line under the text.
|
||||||
|
///
|
||||||
|
/// If you want to control the line color, use [`LayoutJob`] instead.
|
||||||
|
#[inline]
|
||||||
|
pub fn underline(mut self) -> Self {
|
||||||
|
self.underline = true;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Draw a line through the text, crossing it out.
|
||||||
|
///
|
||||||
|
/// If you want to control the strikethrough line color, use [`LayoutJob`] instead.
|
||||||
|
#[inline]
|
||||||
|
pub fn strikethrough(mut self) -> Self {
|
||||||
|
self.strikethrough = true;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Tilt the characters to the right.
|
||||||
|
#[inline]
|
||||||
|
pub fn italics(mut self) -> Self {
|
||||||
|
self.italics = true;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Smaller text.
|
||||||
|
#[inline]
|
||||||
|
pub fn small(self) -> Self {
|
||||||
|
self.text_style(TextStyle::Small)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// For e.g. exponents.
|
||||||
|
#[inline]
|
||||||
|
pub fn small_raised(self) -> Self {
|
||||||
|
self.text_style(TextStyle::Small).raised()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Align text to top. Only applicable together with [`Self::small()`].
|
||||||
|
#[inline]
|
||||||
|
pub fn raised(mut self) -> Self {
|
||||||
|
self.raised = true;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Fill-color behind the text.
|
||||||
|
#[inline]
|
||||||
|
pub fn background_color(mut self, background_color: impl Into<Color32>) -> Self {
|
||||||
|
self.background_color = background_color.into();
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Override text color.
|
||||||
|
#[inline]
|
||||||
|
pub fn color(mut self, color: impl Into<Color32>) -> Self {
|
||||||
|
self.text_color = Some(color.into());
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Read the font height of the selected text style.
|
||||||
|
pub fn font_height(&self, fonts: &epaint::text::Fonts, style: &crate::Style) -> f32 {
|
||||||
|
let text_style = self
|
||||||
|
.text_style
|
||||||
|
.or(style.override_text_style)
|
||||||
|
.unwrap_or(style.body_text_style);
|
||||||
|
fonts.row_height(text_style)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn into_text_job(
|
||||||
|
self,
|
||||||
|
style: &Style,
|
||||||
|
default_text_style: TextStyle,
|
||||||
|
default_valign: Align,
|
||||||
|
) -> WidgetTextJob {
|
||||||
|
let text_color = self.get_text_color(&style.visuals);
|
||||||
|
|
||||||
|
let Self {
|
||||||
|
text,
|
||||||
|
text_style,
|
||||||
|
background_color,
|
||||||
|
text_color: _, // already used by `get_text_color`
|
||||||
|
code,
|
||||||
|
strong: _, // already used by `get_text_color`
|
||||||
|
weak: _, // already used by `get_text_color`
|
||||||
|
strikethrough,
|
||||||
|
underline,
|
||||||
|
italics,
|
||||||
|
raised,
|
||||||
|
} = self;
|
||||||
|
|
||||||
|
let job_has_color = text_color.is_some();
|
||||||
|
let line_color = text_color.unwrap_or_else(|| style.visuals.text_color());
|
||||||
|
let text_color = text_color.unwrap_or(crate::Color32::TEMPORARY_COLOR);
|
||||||
|
|
||||||
|
let text_style = text_style
|
||||||
|
.or(style.override_text_style)
|
||||||
|
.unwrap_or(default_text_style);
|
||||||
|
|
||||||
|
let mut background_color = background_color;
|
||||||
|
if code {
|
||||||
|
background_color = style.visuals.code_bg_color;
|
||||||
|
}
|
||||||
|
let underline = if underline {
|
||||||
|
crate::Stroke::new(1.0, line_color)
|
||||||
|
} else {
|
||||||
|
crate::Stroke::none()
|
||||||
|
};
|
||||||
|
let strikethrough = if strikethrough {
|
||||||
|
crate::Stroke::new(1.0, line_color)
|
||||||
|
} else {
|
||||||
|
crate::Stroke::none()
|
||||||
|
};
|
||||||
|
|
||||||
|
let valign = if raised {
|
||||||
|
crate::Align::TOP
|
||||||
|
} else {
|
||||||
|
default_valign
|
||||||
|
};
|
||||||
|
|
||||||
|
let text_format = crate::text::TextFormat {
|
||||||
|
style: text_style,
|
||||||
|
color: text_color,
|
||||||
|
background: background_color,
|
||||||
|
italics,
|
||||||
|
underline,
|
||||||
|
strikethrough,
|
||||||
|
valign,
|
||||||
|
};
|
||||||
|
|
||||||
|
let job = LayoutJob::single_section(text, text_format);
|
||||||
|
WidgetTextJob { job, job_has_color }
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_text_color(&self, visuals: &Visuals) -> Option<Color32> {
|
||||||
|
if let Some(text_color) = self.text_color {
|
||||||
|
Some(text_color)
|
||||||
|
} else if self.strong {
|
||||||
|
Some(visuals.strong_text_color())
|
||||||
|
} else if self.weak {
|
||||||
|
Some(visuals.weak_text_color())
|
||||||
|
} else {
|
||||||
|
visuals.override_text_color
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
/// This is how you specify text for a widget.
|
||||||
|
///
|
||||||
|
/// A lot of widgets use `impl Into<WidgetText>` as an argument,
|
||||||
|
/// allowing you to pass in [`String`], [`RichText`], [`LayoutJob`], and more.
|
||||||
|
///
|
||||||
|
/// Often a [`WidgetText`] is just a simple [`String`],
|
||||||
|
/// but it can be a [`RichText`] (text with color, style, etc),
|
||||||
|
/// a [`LayoutJob`] (for when you want full control of how the text looks)
|
||||||
|
/// or text that has already been layed out in a [`Galley`].
|
||||||
|
pub enum WidgetText {
|
||||||
|
RichText(RichText),
|
||||||
|
/// Use this [`LayoutJob`] when laying out the text.
|
||||||
|
///
|
||||||
|
/// Only [`LayoutJob::text`] and [`LayoutJob::sections`] are guaranteed to be respected.
|
||||||
|
///
|
||||||
|
/// [`LayoutJob::wrap_width`], [`LayoutJob::halign`], [`LayoutJob::justify`]
|
||||||
|
/// and [`LayoutJob::first_row_min_height`] will likely be determined by the [`crate::Layout`]
|
||||||
|
/// of the [`Ui`] the widget is placed in.
|
||||||
|
/// If you want all parts of the `LayoutJob` respected, then convert it to a
|
||||||
|
/// [`Galley`] and use [`Self::Galley`] instead.
|
||||||
|
LayoutJob(LayoutJob),
|
||||||
|
/// Use exactly this galley when painting the text.
|
||||||
|
Galley(Arc<Galley>),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for WidgetText {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::RichText(RichText::default())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl WidgetText {
|
||||||
|
#[inline]
|
||||||
|
pub fn is_empty(&self) -> bool {
|
||||||
|
match self {
|
||||||
|
Self::RichText(text) => text.is_empty(),
|
||||||
|
Self::LayoutJob(job) => job.is_empty(),
|
||||||
|
Self::Galley(galley) => galley.is_empty(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn text(&self) -> &str {
|
||||||
|
match self {
|
||||||
|
Self::RichText(text) => text.text(),
|
||||||
|
Self::LayoutJob(job) => &job.text,
|
||||||
|
Self::Galley(galley) => galley.text(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Override the [`TextStyle`] if, and only if, this is a [`RichText`].
|
||||||
|
///
|
||||||
|
/// Prefer using [`RichText`] directly!
|
||||||
|
#[inline]
|
||||||
|
pub fn text_style(self, text_style: TextStyle) -> Self {
|
||||||
|
match self {
|
||||||
|
Self::RichText(text) => Self::RichText(text.text_style(text_style)),
|
||||||
|
Self::LayoutJob(_) | Self::Galley(_) => self,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set the [`TextStyle`] unless it has already been set
|
||||||
|
///
|
||||||
|
/// Prefer using [`RichText`] directly!
|
||||||
|
#[inline]
|
||||||
|
pub fn fallback_text_style(self, text_style: TextStyle) -> Self {
|
||||||
|
match self {
|
||||||
|
Self::RichText(text) => Self::RichText(text.fallback_text_style(text_style)),
|
||||||
|
Self::LayoutJob(_) | Self::Galley(_) => self,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Override text color if, and only if, this is a [`RichText`].
|
||||||
|
///
|
||||||
|
/// Prefer using [`RichText`] directly!
|
||||||
|
#[inline]
|
||||||
|
pub fn color(self, color: impl Into<Color32>) -> Self {
|
||||||
|
match self {
|
||||||
|
Self::RichText(text) => Self::RichText(text.color(color)),
|
||||||
|
Self::LayoutJob(_) | Self::Galley(_) => self,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Prefer using [`RichText`] directly!
|
||||||
|
pub fn heading(self) -> Self {
|
||||||
|
match self {
|
||||||
|
Self::RichText(text) => Self::RichText(text.heading()),
|
||||||
|
Self::LayoutJob(_) | Self::Galley(_) => self,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Prefer using [`RichText`] directly!
|
||||||
|
pub fn monospace(self) -> Self {
|
||||||
|
match self {
|
||||||
|
Self::RichText(text) => Self::RichText(text.monospace()),
|
||||||
|
Self::LayoutJob(_) | Self::Galley(_) => self,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Prefer using [`RichText`] directly!
|
||||||
|
pub fn code(self) -> Self {
|
||||||
|
match self {
|
||||||
|
Self::RichText(text) => Self::RichText(text.code()),
|
||||||
|
Self::LayoutJob(_) | Self::Galley(_) => self,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Prefer using [`RichText`] directly!
|
||||||
|
pub fn strong(self) -> Self {
|
||||||
|
match self {
|
||||||
|
Self::RichText(text) => Self::RichText(text.strong()),
|
||||||
|
Self::LayoutJob(_) | Self::Galley(_) => self,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Prefer using [`RichText`] directly!
|
||||||
|
pub fn weak(self) -> Self {
|
||||||
|
match self {
|
||||||
|
Self::RichText(text) => Self::RichText(text.weak()),
|
||||||
|
Self::LayoutJob(_) | Self::Galley(_) => self,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Prefer using [`RichText`] directly!
|
||||||
|
pub fn underline(self) -> Self {
|
||||||
|
match self {
|
||||||
|
Self::RichText(text) => Self::RichText(text.underline()),
|
||||||
|
Self::LayoutJob(_) | Self::Galley(_) => self,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Prefer using [`RichText`] directly!
|
||||||
|
pub fn strikethrough(self) -> Self {
|
||||||
|
match self {
|
||||||
|
Self::RichText(text) => Self::RichText(text.strikethrough()),
|
||||||
|
Self::LayoutJob(_) | Self::Galley(_) => self,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Prefer using [`RichText`] directly!
|
||||||
|
pub fn italics(self) -> Self {
|
||||||
|
match self {
|
||||||
|
Self::RichText(text) => Self::RichText(text.italics()),
|
||||||
|
Self::LayoutJob(_) | Self::Galley(_) => self,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Prefer using [`RichText`] directly!
|
||||||
|
pub fn small(self) -> Self {
|
||||||
|
match self {
|
||||||
|
Self::RichText(text) => Self::RichText(text.small()),
|
||||||
|
Self::LayoutJob(_) | Self::Galley(_) => self,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Prefer using [`RichText`] directly!
|
||||||
|
pub fn small_raised(self) -> Self {
|
||||||
|
match self {
|
||||||
|
Self::RichText(text) => Self::RichText(text.small_raised()),
|
||||||
|
Self::LayoutJob(_) | Self::Galley(_) => self,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Prefer using [`RichText`] directly!
|
||||||
|
pub fn raised(self) -> Self {
|
||||||
|
match self {
|
||||||
|
Self::RichText(text) => Self::RichText(text.raised()),
|
||||||
|
Self::LayoutJob(_) | Self::Galley(_) => self,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Prefer using [`RichText`] directly!
|
||||||
|
pub fn background_color(self, background_color: impl Into<Color32>) -> Self {
|
||||||
|
match self {
|
||||||
|
Self::RichText(text) => Self::RichText(text.background_color(background_color)),
|
||||||
|
Self::LayoutJob(_) | Self::Galley(_) => self,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn font_height(&self, fonts: &epaint::text::Fonts, style: &crate::Style) -> f32 {
|
||||||
|
match self {
|
||||||
|
Self::RichText(text) => text.font_height(fonts, style),
|
||||||
|
Self::LayoutJob(job) => job.font_height(fonts),
|
||||||
|
Self::Galley(galley) => {
|
||||||
|
if let Some(row) = galley.rows.first() {
|
||||||
|
row.height()
|
||||||
|
} else {
|
||||||
|
galley.size().y
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn into_text_job(
|
||||||
|
self,
|
||||||
|
style: &Style,
|
||||||
|
default_text_style: TextStyle,
|
||||||
|
default_valign: Align,
|
||||||
|
) -> WidgetTextJob {
|
||||||
|
match self {
|
||||||
|
Self::RichText(text) => text.into_text_job(style, default_text_style, default_valign),
|
||||||
|
Self::LayoutJob(job) => WidgetTextJob {
|
||||||
|
job,
|
||||||
|
job_has_color: true,
|
||||||
|
},
|
||||||
|
Self::Galley(galley) => {
|
||||||
|
let job: LayoutJob = (*galley.job).clone();
|
||||||
|
WidgetTextJob {
|
||||||
|
job,
|
||||||
|
job_has_color: true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Layout with wrap mode based on the containing `Ui`.
|
||||||
|
///
|
||||||
|
/// wrap: override for [`Ui::wrap_text`].
|
||||||
|
pub fn into_galley(
|
||||||
|
self,
|
||||||
|
ui: &Ui,
|
||||||
|
wrap: Option<bool>,
|
||||||
|
available_width: f32,
|
||||||
|
default_text_style: TextStyle,
|
||||||
|
) -> WidgetTextGalley {
|
||||||
|
let wrap = wrap.unwrap_or_else(|| ui.wrap_text());
|
||||||
|
let wrap_width = if wrap { available_width } else { f32::INFINITY };
|
||||||
|
|
||||||
|
match self {
|
||||||
|
Self::RichText(text) => {
|
||||||
|
let valign = ui.layout().vertical_align();
|
||||||
|
let mut text_job = text.into_text_job(ui.style(), default_text_style, valign);
|
||||||
|
text_job.job.wrap_width = wrap_width;
|
||||||
|
WidgetTextGalley {
|
||||||
|
galley: ui.fonts().layout_job(text_job.job),
|
||||||
|
galley_has_color: text_job.job_has_color,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Self::LayoutJob(mut job) => {
|
||||||
|
job.wrap_width = wrap_width;
|
||||||
|
WidgetTextGalley {
|
||||||
|
galley: ui.fonts().layout_job(job),
|
||||||
|
galley_has_color: true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Self::Galley(galley) => WidgetTextGalley {
|
||||||
|
galley,
|
||||||
|
galley_has_color: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<&str> for WidgetText {
|
||||||
|
#[inline]
|
||||||
|
fn from(text: &str) -> Self {
|
||||||
|
Self::RichText(RichText::new(text))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<&String> for WidgetText {
|
||||||
|
#[inline]
|
||||||
|
fn from(text: &String) -> Self {
|
||||||
|
Self::RichText(RichText::new(text))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<String> for WidgetText {
|
||||||
|
#[inline]
|
||||||
|
fn from(text: String) -> Self {
|
||||||
|
Self::RichText(RichText::new(text))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<RichText> for WidgetText {
|
||||||
|
#[inline]
|
||||||
|
fn from(rich_text: RichText) -> Self {
|
||||||
|
Self::RichText(rich_text)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<LayoutJob> for WidgetText {
|
||||||
|
#[inline]
|
||||||
|
fn from(layout_job: LayoutJob) -> Self {
|
||||||
|
Self::LayoutJob(layout_job)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<Arc<Galley>> for WidgetText {
|
||||||
|
#[inline]
|
||||||
|
fn from(galley: Arc<Galley>) -> Self {
|
||||||
|
Self::Galley(galley)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
pub struct WidgetTextJob {
|
||||||
|
pub job: LayoutJob,
|
||||||
|
pub job_has_color: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl WidgetTextJob {
|
||||||
|
pub fn into_galley(self, fonts: &crate::text::Fonts) -> WidgetTextGalley {
|
||||||
|
let Self { job, job_has_color } = self;
|
||||||
|
let galley = fonts.layout_job(job);
|
||||||
|
WidgetTextGalley {
|
||||||
|
galley,
|
||||||
|
galley_has_color: job_has_color,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
/// Text that has been layed out and ready to be painted.
|
||||||
|
pub struct WidgetTextGalley {
|
||||||
|
pub galley: Arc<Galley>,
|
||||||
|
pub galley_has_color: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl WidgetTextGalley {
|
||||||
|
/// Size of the layed out text.
|
||||||
|
#[inline]
|
||||||
|
pub fn size(&self) -> crate::Vec2 {
|
||||||
|
self.galley.size()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Size of the layed out text.
|
||||||
|
#[inline]
|
||||||
|
pub fn text(&self) -> &str {
|
||||||
|
self.galley.text()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn galley(&self) -> &Arc<Galley> {
|
||||||
|
&self.galley
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Use the colors in the original [`WidgetText`] if any,
|
||||||
|
/// else fall back to the one specified by the [`WidgetVisuals`].
|
||||||
|
pub fn paint_with_visuals(
|
||||||
|
self,
|
||||||
|
painter: &crate::Painter,
|
||||||
|
text_pos: Pos2,
|
||||||
|
visuals: &WidgetVisuals,
|
||||||
|
) {
|
||||||
|
self.paint_with_fallback_color(painter, text_pos, visuals.text_color());
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Use the colors in the original [`WidgetText`] if any,
|
||||||
|
/// else fall back to the given color.
|
||||||
|
pub fn paint_with_fallback_color(
|
||||||
|
self,
|
||||||
|
painter: &crate::Painter,
|
||||||
|
text_pos: Pos2,
|
||||||
|
text_color: Color32,
|
||||||
|
) {
|
||||||
|
if self.galley_has_color {
|
||||||
|
painter.galley(text_pos, self.galley);
|
||||||
|
} else {
|
||||||
|
painter.galley_with_color(text_pos, self.galley, text_color);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Paint with this specific color.
|
||||||
|
pub fn paint_with_color_override(
|
||||||
|
self,
|
||||||
|
painter: &crate::Painter,
|
||||||
|
text_pos: Pos2,
|
||||||
|
text_color: Color32,
|
||||||
|
) {
|
||||||
|
painter.galley_with_color(text_pos, self.galley, text_color);
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,14 +1,5 @@
|
||||||
use crate::*;
|
use crate::*;
|
||||||
|
|
||||||
/// For those of us who miss `a ? yes : no`.
|
|
||||||
fn select<T>(b: bool, if_true: T, if_false: T) -> T {
|
|
||||||
if b {
|
|
||||||
if_true
|
|
||||||
} else {
|
|
||||||
if_false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Clickable button with text.
|
/// Clickable button with text.
|
||||||
///
|
///
|
||||||
/// See also [`Ui::button`].
|
/// See also [`Ui::button`].
|
||||||
|
@ -17,7 +8,7 @@ fn select<T>(b: bool, if_true: T, if_false: T) -> T {
|
||||||
/// # let ui = &mut egui::Ui::__test();
|
/// # let ui = &mut egui::Ui::__test();
|
||||||
/// # fn do_stuff() {}
|
/// # fn do_stuff() {}
|
||||||
///
|
///
|
||||||
/// if ui.add(egui::Button::new("Click mew")).clicked() {
|
/// if ui.add(egui::Button::new("Click me")).clicked() {
|
||||||
/// do_stuff();
|
/// do_stuff();
|
||||||
/// }
|
/// }
|
||||||
///
|
///
|
||||||
|
@ -28,48 +19,53 @@ 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: WidgetText,
|
||||||
text_color: Option<Color32>,
|
wrap: Option<bool>,
|
||||||
text_style: Option<TextStyle>,
|
|
||||||
/// None means default for interact
|
/// None means default for interact
|
||||||
fill: Option<Color32>,
|
fill: Option<Color32>,
|
||||||
stroke: Option<Stroke>,
|
stroke: Option<Stroke>,
|
||||||
sense: Sense,
|
sense: Sense,
|
||||||
small: bool,
|
small: bool,
|
||||||
frame: Option<bool>,
|
frame: Option<bool>,
|
||||||
wrap: Option<bool>,
|
|
||||||
min_size: Vec2,
|
min_size: Vec2,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Button {
|
impl Button {
|
||||||
#[allow(clippy::needless_pass_by_value)]
|
pub fn new(text: impl Into<WidgetText>) -> Self {
|
||||||
pub fn new(text: impl ToString) -> Self {
|
|
||||||
Self {
|
Self {
|
||||||
text: text.to_string(),
|
text: text.into(),
|
||||||
text_color: None,
|
wrap: None,
|
||||||
text_style: None,
|
|
||||||
fill: None,
|
fill: None,
|
||||||
stroke: None,
|
stroke: None,
|
||||||
sense: Sense::click(),
|
sense: Sense::click(),
|
||||||
small: false,
|
small: false,
|
||||||
frame: None,
|
frame: None,
|
||||||
wrap: None,
|
|
||||||
min_size: Vec2::ZERO,
|
min_size: Vec2::ZERO,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// If `true`, the text will wrap to stay within the max width of the `Ui`.
|
||||||
|
///
|
||||||
|
/// By default [`Self::wrap`] will be true in vertical layouts
|
||||||
|
/// and horizontal layouts with wrapping,
|
||||||
|
/// and false on non-wrapping horizontal layouts.
|
||||||
|
///
|
||||||
|
/// Note that any `\n` in the text will always produce a new line.
|
||||||
|
#[inline]
|
||||||
|
pub fn wrap(mut self, wrap: bool) -> Self {
|
||||||
|
self.wrap = Some(wrap);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
#[deprecated = "Replaced by: Button::new(RichText::new(text).color(…))"]
|
||||||
pub fn text_color(mut self, text_color: Color32) -> Self {
|
pub fn text_color(mut self, text_color: Color32) -> Self {
|
||||||
self.text_color = Some(text_color);
|
self.text = self.text.color(text_color);
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn text_color_opt(mut self, text_color: Option<Color32>) -> Self {
|
|
||||||
self.text_color = text_color;
|
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[deprecated = "Replaced by: Button::new(RichText::new(text).text_style(…))"]
|
||||||
pub fn text_style(mut self, text_style: TextStyle) -> Self {
|
pub fn text_style(mut self, text_style: TextStyle) -> Self {
|
||||||
self.text_style = Some(text_style);
|
self.text = self.text.text_style(text_style);
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -91,7 +87,7 @@ impl Button {
|
||||||
|
|
||||||
/// Make this a small button, suitable for embedding into text.
|
/// Make this a small button, suitable for embedding into text.
|
||||||
pub fn small(mut self) -> Self {
|
pub fn small(mut self) -> Self {
|
||||||
self.text_style = Some(TextStyle::Body);
|
self.text = self.text.text_style(TextStyle::Body);
|
||||||
self.small = true;
|
self.small = true;
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
@ -109,17 +105,6 @@ impl Button {
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
/// If `true`, the text will wrap at the `max_width`.
|
|
||||||
/// By default [`Self::wrap`] will be true in vertical layouts
|
|
||||||
/// and horizontal layouts with wrapping,
|
|
||||||
/// and false on non-wrapping horizontal layouts.
|
|
||||||
///
|
|
||||||
/// Note that any `\n` in the button text will always produce a new line.
|
|
||||||
pub fn wrap(mut self, wrap: bool) -> Self {
|
|
||||||
self.wrap = Some(wrap);
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn min_size(mut self, min_size: Vec2) -> Self {
|
pub(crate) fn min_size(mut self, min_size: Vec2) -> Self {
|
||||||
self.min_size = min_size;
|
self.min_size = min_size;
|
||||||
self
|
self
|
||||||
|
@ -130,49 +115,40 @@ impl Widget for Button {
|
||||||
fn ui(self, ui: &mut Ui) -> Response {
|
fn ui(self, ui: &mut Ui) -> Response {
|
||||||
let Button {
|
let Button {
|
||||||
text,
|
text,
|
||||||
text_color,
|
wrap,
|
||||||
text_style,
|
|
||||||
fill,
|
fill,
|
||||||
stroke,
|
stroke,
|
||||||
sense,
|
sense,
|
||||||
small,
|
small,
|
||||||
frame,
|
frame,
|
||||||
wrap,
|
|
||||||
min_size,
|
min_size,
|
||||||
} = self;
|
} = self;
|
||||||
|
|
||||||
let frame = frame.unwrap_or_else(|| ui.visuals().button_frame);
|
let frame = frame.unwrap_or_else(|| ui.visuals().button_frame);
|
||||||
|
|
||||||
let text_style = text_style
|
|
||||||
.or(ui.style().override_text_style)
|
|
||||||
.unwrap_or(TextStyle::Button);
|
|
||||||
|
|
||||||
let mut button_padding = ui.spacing().button_padding;
|
let mut button_padding = ui.spacing().button_padding;
|
||||||
if small {
|
if small {
|
||||||
button_padding.y = 0.0;
|
button_padding.y = 0.0;
|
||||||
}
|
}
|
||||||
let total_extra = button_padding + button_padding;
|
let total_extra = button_padding + button_padding;
|
||||||
|
|
||||||
let wrap = wrap.unwrap_or_else(|| ui.wrap_text());
|
let wrap_width = ui.available_width() - total_extra.x;
|
||||||
let wrap_width = select(wrap, ui.available_width() - total_extra.x, f32::INFINITY);
|
let text = text.into_galley(ui, wrap, wrap_width, TextStyle::Button);
|
||||||
let galley = ui
|
|
||||||
.fonts()
|
|
||||||
.layout_delayed_color(text, text_style, wrap_width);
|
|
||||||
|
|
||||||
let mut desired_size = galley.size() + 2.0 * button_padding;
|
let mut desired_size = text.size() + 2.0 * button_padding;
|
||||||
if !small {
|
if !small {
|
||||||
desired_size.y = desired_size.y.at_least(ui.spacing().interact_size.y);
|
desired_size.y = desired_size.y.at_least(ui.spacing().interact_size.y);
|
||||||
}
|
}
|
||||||
desired_size = desired_size.at_least(min_size);
|
desired_size = desired_size.at_least(min_size);
|
||||||
|
|
||||||
let (rect, response) = ui.allocate_at_least(desired_size, sense);
|
let (rect, response) = ui.allocate_at_least(desired_size, sense);
|
||||||
response.widget_info(|| WidgetInfo::labeled(WidgetType::Button, galley.text()));
|
response.widget_info(|| WidgetInfo::labeled(WidgetType::Button, text.text()));
|
||||||
|
|
||||||
if ui.clip_rect().intersects(rect) {
|
if ui.clip_rect().intersects(rect) {
|
||||||
let visuals = ui.style().interact(&response);
|
let visuals = ui.style().interact(&response);
|
||||||
let text_pos = ui
|
let text_pos = ui
|
||||||
.layout()
|
.layout()
|
||||||
.align_size_within_rect(galley.size(), rect.shrink2(button_padding))
|
.align_size_within_rect(text.size(), rect.shrink2(button_padding))
|
||||||
.min;
|
.min;
|
||||||
|
|
||||||
if frame {
|
if frame {
|
||||||
|
@ -186,10 +162,7 @@ impl Widget for Button {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
let text_color = text_color
|
text.paint_with_visuals(ui.painter(), text_pos, visuals);
|
||||||
.or(ui.visuals().override_text_color)
|
|
||||||
.unwrap_or_else(|| visuals.text_color());
|
|
||||||
ui.painter().galley_with_color(text_pos, galley, text_color);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
response
|
response
|
||||||
|
@ -211,48 +184,35 @@ impl Widget for Button {
|
||||||
/// ui.add(egui::Checkbox::new(&mut my_bool, "Checked"));
|
/// ui.add(egui::Checkbox::new(&mut my_bool, "Checked"));
|
||||||
/// ```
|
/// ```
|
||||||
#[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);`"]
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct Checkbox<'a> {
|
pub struct Checkbox<'a> {
|
||||||
checked: &'a mut bool,
|
checked: &'a mut bool,
|
||||||
text: String,
|
text: WidgetText,
|
||||||
text_color: Option<Color32>,
|
|
||||||
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<WidgetText>) -> 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_style: None,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[deprecated = "Replaced by: Checkbox::new(RichText::new(text).color(…))"]
|
||||||
pub fn text_color(mut self, text_color: Color32) -> Self {
|
pub fn text_color(mut self, text_color: Color32) -> Self {
|
||||||
self.text_color = Some(text_color);
|
self.text = self.text.color(text_color);
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[deprecated = "Replaced by: Checkbox::new(RichText::new(text).text_style(…))"]
|
||||||
pub fn text_style(mut self, text_style: TextStyle) -> Self {
|
pub fn text_style(mut self, text_style: TextStyle) -> Self {
|
||||||
self.text_style = Some(text_style);
|
self.text = self.text.text_style(text_style);
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> Widget for Checkbox<'a> {
|
impl<'a> Widget for Checkbox<'a> {
|
||||||
fn ui(self, ui: &mut Ui) -> Response {
|
fn ui(self, ui: &mut Ui) -> Response {
|
||||||
let Checkbox {
|
let Checkbox { checked, text } = self;
|
||||||
checked,
|
|
||||||
text,
|
|
||||||
text_color,
|
|
||||||
text_style,
|
|
||||||
} = self;
|
|
||||||
|
|
||||||
let text_style = text_style
|
|
||||||
.or(ui.style().override_text_style)
|
|
||||||
.unwrap_or(TextStyle::Button);
|
|
||||||
|
|
||||||
let spacing = &ui.spacing();
|
let spacing = &ui.spacing();
|
||||||
let icon_width = spacing.icon_width;
|
let icon_width = spacing.icon_width;
|
||||||
|
@ -260,16 +220,10 @@ impl<'a> Widget for Checkbox<'a> {
|
||||||
let button_padding = spacing.button_padding;
|
let button_padding = spacing.button_padding;
|
||||||
let total_extra = button_padding + vec2(icon_width + icon_spacing, 0.0) + button_padding;
|
let total_extra = button_padding + vec2(icon_width + icon_spacing, 0.0) + button_padding;
|
||||||
|
|
||||||
let wrap_width = select(
|
let wrap_width = ui.available_width() - total_extra.x;
|
||||||
ui.wrap_text(),
|
let text = text.into_galley(ui, None, wrap_width, TextStyle::Button);
|
||||||
ui.available_width() - total_extra.x,
|
|
||||||
f32::INFINITY,
|
|
||||||
);
|
|
||||||
let galley = ui
|
|
||||||
.fonts()
|
|
||||||
.layout_delayed_color(text, text_style, wrap_width);
|
|
||||||
|
|
||||||
let mut desired_size = total_extra + galley.size();
|
let mut desired_size = total_extra + text.size();
|
||||||
desired_size = desired_size.at_least(spacing.interact_size);
|
desired_size = desired_size.at_least(spacing.interact_size);
|
||||||
desired_size.y = desired_size.y.max(icon_width);
|
desired_size.y = desired_size.y.max(icon_width);
|
||||||
let (rect, mut response) = ui.allocate_exact_size(desired_size, Sense::click());
|
let (rect, mut response) = ui.allocate_exact_size(desired_size, Sense::click());
|
||||||
|
@ -278,14 +232,13 @@ impl<'a> Widget for Checkbox<'a> {
|
||||||
*checked = !*checked;
|
*checked = !*checked;
|
||||||
response.mark_changed();
|
response.mark_changed();
|
||||||
}
|
}
|
||||||
response
|
response.widget_info(|| WidgetInfo::selected(WidgetType::Checkbox, *checked, text.text()));
|
||||||
.widget_info(|| WidgetInfo::selected(WidgetType::Checkbox, *checked, galley.text()));
|
|
||||||
|
|
||||||
// let visuals = ui.style().interact_selectable(&response, *checked); // too colorful
|
// let visuals = ui.style().interact_selectable(&response, *checked); // too colorful
|
||||||
let visuals = ui.style().interact(&response);
|
let visuals = ui.style().interact(&response);
|
||||||
let text_pos = pos2(
|
let text_pos = pos2(
|
||||||
rect.min.x + button_padding.x + icon_width + icon_spacing,
|
rect.min.x + button_padding.x + icon_width + icon_spacing,
|
||||||
rect.center().y - 0.5 * galley.size().y,
|
rect.center().y - 0.5 * text.size().y,
|
||||||
);
|
);
|
||||||
let (small_icon_rect, big_icon_rect) = ui.spacing().icon_rectangles(rect);
|
let (small_icon_rect, big_icon_rect) = ui.spacing().icon_rectangles(rect);
|
||||||
ui.painter().add(epaint::RectShape {
|
ui.painter().add(epaint::RectShape {
|
||||||
|
@ -307,10 +260,7 @@ impl<'a> Widget for Checkbox<'a> {
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
let text_color = text_color
|
text.paint_with_visuals(ui.painter(), text_pos, visuals);
|
||||||
.or(ui.visuals().override_text_color)
|
|
||||||
.unwrap_or_else(|| visuals.text_color());
|
|
||||||
ui.painter().galley_with_color(text_pos, galley, text_color);
|
|
||||||
response
|
response
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -336,73 +286,54 @@ impl<'a> Widget for Checkbox<'a> {
|
||||||
/// }
|
/// }
|
||||||
/// ```
|
/// ```
|
||||||
#[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);`"]
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct RadioButton {
|
pub struct RadioButton {
|
||||||
checked: bool,
|
checked: bool,
|
||||||
text: String,
|
text: WidgetText,
|
||||||
text_color: Option<Color32>,
|
|
||||||
text_style: Option<TextStyle>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl RadioButton {
|
impl RadioButton {
|
||||||
#[allow(clippy::needless_pass_by_value)]
|
pub fn new(checked: bool, text: impl Into<WidgetText>) -> 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_style: None,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[deprecated = "Replaced by: RadioButton::new(RichText::new(text).color(…))"]
|
||||||
pub fn text_color(mut self, text_color: Color32) -> Self {
|
pub fn text_color(mut self, text_color: Color32) -> Self {
|
||||||
self.text_color = Some(text_color);
|
self.text = self.text.color(text_color);
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[deprecated = "Replaced by: RadioButton::new(RichText::new(text).text_style(…))"]
|
||||||
pub fn text_style(mut self, text_style: TextStyle) -> Self {
|
pub fn text_style(mut self, text_style: TextStyle) -> Self {
|
||||||
self.text_style = Some(text_style);
|
self.text = self.text.text_style(text_style);
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Widget for RadioButton {
|
impl Widget for RadioButton {
|
||||||
fn ui(self, ui: &mut Ui) -> Response {
|
fn ui(self, ui: &mut Ui) -> Response {
|
||||||
let RadioButton {
|
let RadioButton { checked, text } = self;
|
||||||
checked,
|
|
||||||
text,
|
|
||||||
text_color,
|
|
||||||
text_style,
|
|
||||||
} = self;
|
|
||||||
|
|
||||||
let text_style = text_style
|
|
||||||
.or(ui.style().override_text_style)
|
|
||||||
.unwrap_or(TextStyle::Button);
|
|
||||||
|
|
||||||
let icon_width = ui.spacing().icon_width;
|
let icon_width = ui.spacing().icon_width;
|
||||||
let icon_spacing = ui.spacing().icon_spacing;
|
let icon_spacing = ui.spacing().icon_spacing;
|
||||||
let button_padding = ui.spacing().button_padding;
|
let button_padding = ui.spacing().button_padding;
|
||||||
let total_extra = button_padding + vec2(icon_width + icon_spacing, 0.0) + button_padding;
|
let total_extra = button_padding + vec2(icon_width + icon_spacing, 0.0) + button_padding;
|
||||||
|
|
||||||
let wrap_width = select(
|
let wrap_width = ui.available_width() - total_extra.x;
|
||||||
ui.wrap_text(),
|
let text = text.into_galley(ui, None, wrap_width, TextStyle::Button);
|
||||||
ui.available_width() - total_extra.x,
|
|
||||||
f32::INFINITY,
|
|
||||||
);
|
|
||||||
let galley = ui
|
|
||||||
.fonts()
|
|
||||||
.layout_delayed_color(text, text_style, wrap_width);
|
|
||||||
|
|
||||||
let mut desired_size = total_extra + galley.size();
|
let mut desired_size = total_extra + text.size();
|
||||||
desired_size = desired_size.at_least(ui.spacing().interact_size);
|
desired_size = desired_size.at_least(ui.spacing().interact_size);
|
||||||
desired_size.y = desired_size.y.max(icon_width);
|
desired_size.y = desired_size.y.max(icon_width);
|
||||||
let (rect, response) = ui.allocate_exact_size(desired_size, Sense::click());
|
let (rect, response) = ui.allocate_exact_size(desired_size, Sense::click());
|
||||||
response
|
response
|
||||||
.widget_info(|| WidgetInfo::selected(WidgetType::RadioButton, checked, galley.text()));
|
.widget_info(|| WidgetInfo::selected(WidgetType::RadioButton, checked, text.text()));
|
||||||
|
|
||||||
let text_pos = pos2(
|
let text_pos = pos2(
|
||||||
rect.min.x + button_padding.x + icon_width + icon_spacing,
|
rect.min.x + button_padding.x + icon_width + icon_spacing,
|
||||||
rect.center().y - 0.5 * galley.size().y,
|
rect.center().y - 0.5 * text.size().y,
|
||||||
);
|
);
|
||||||
|
|
||||||
// let visuals = ui.style().interact_selectable(&response, checked); // too colorful
|
// let visuals = ui.style().interact_selectable(&response, checked); // too colorful
|
||||||
|
@ -429,10 +360,7 @@ impl Widget for RadioButton {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
let text_color = text_color
|
text.paint_with_visuals(ui.painter(), text_pos, visuals);
|
||||||
.or(ui.visuals().override_text_color)
|
|
||||||
.unwrap_or_else(|| visuals.text_color());
|
|
||||||
painter.galley_with_color(text_pos, galley, text_color);
|
|
||||||
response
|
response
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -208,11 +208,12 @@ impl<'a> Widget for DragValue<'a> {
|
||||||
}
|
}
|
||||||
response
|
response
|
||||||
} else {
|
} else {
|
||||||
let button = Button::new(format!("{}{}{}", prefix, value_text, suffix))
|
let button = Button::new(
|
||||||
.sense(Sense::click_and_drag())
|
RichText::new(format!("{}{}{}", prefix, value_text, suffix)).monospace(),
|
||||||
.text_style(TextStyle::Monospace)
|
)
|
||||||
.wrap(false)
|
.wrap(false)
|
||||||
.min_size(ui.spacing().interact_size); // TODO: find some more generic solution to this
|
.sense(Sense::click_and_drag())
|
||||||
|
.min_size(ui.spacing().interact_size); // TODO: find some more generic solution to `min_size`
|
||||||
|
|
||||||
let response = ui.add(button);
|
let response = ui.add(button);
|
||||||
let mut response = response.on_hover_cursor(CursorIcon::ResizeHorizontal);
|
let mut response = response.on_hover_cursor(CursorIcon::ResizeHorizontal);
|
||||||
|
|
|
@ -12,7 +12,7 @@ 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: String,
|
||||||
label: Label,
|
text: WidgetText,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Hyperlink {
|
impl Hyperlink {
|
||||||
|
@ -21,41 +21,45 @@ impl Hyperlink {
|
||||||
let url = url.to_string();
|
let url = url.to_string();
|
||||||
Self {
|
Self {
|
||||||
url: url.clone(),
|
url: url.clone(),
|
||||||
label: Label::new(url).sense(Sense::click()),
|
text: url.into(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(clippy::needless_pass_by_value)]
|
#[allow(clippy::needless_pass_by_value)]
|
||||||
pub fn from_label_and_url(label: impl Into<Label>, url: impl ToString) -> Self {
|
pub fn from_label_and_url(text: impl Into<WidgetText>, url: impl ToString) -> Self {
|
||||||
Self {
|
Self {
|
||||||
url: url.to_string(),
|
url: url.to_string(),
|
||||||
label: label.into(),
|
text: text.into(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Show some other text than the url
|
#[deprecated = "Use Hyperlink::from_label_and_url instead"]
|
||||||
#[allow(clippy::needless_pass_by_value)]
|
#[allow(clippy::needless_pass_by_value)]
|
||||||
pub fn text(mut self, text: impl ToString) -> Self {
|
pub fn text(mut self, text: impl ToString) -> Self {
|
||||||
self.label.text = text.to_string();
|
self.text = text.to_string().into();
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The default is [`Style::body_text_style`] (generally [`TextStyle::Body`]).
|
#[deprecated = "Use Hyperlink::from_label_and_url instead"]
|
||||||
pub fn text_style(mut self, text_style: TextStyle) -> Self {
|
pub fn text_style(mut self, text_style: TextStyle) -> Self {
|
||||||
self.label = self.label.text_style(text_style);
|
self.text = self.text.text_style(text_style);
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn small(self) -> Self {
|
#[deprecated = "Use Hyperlink::from_label_and_url instead"]
|
||||||
self.text_style(TextStyle::Small)
|
pub fn small(mut self) -> Self {
|
||||||
|
self.text = self.text.text_style(TextStyle::Small);
|
||||||
|
self
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Widget for Hyperlink {
|
impl Widget for Hyperlink {
|
||||||
fn ui(self, ui: &mut Ui) -> Response {
|
fn ui(self, ui: &mut Ui) -> Response {
|
||||||
let Hyperlink { url, label } = self;
|
let Hyperlink { url, text } = self;
|
||||||
let (pos, galley, response) = label.layout_in_ui(ui);
|
let label = Label::new(text).sense(Sense::click());
|
||||||
response.widget_info(|| WidgetInfo::labeled(WidgetType::Hyperlink, galley.text()));
|
|
||||||
|
let (pos, text_galley, response) = label.layout_in_ui(ui);
|
||||||
|
response.widget_info(|| WidgetInfo::labeled(WidgetType::Hyperlink, text_galley.text()));
|
||||||
|
|
||||||
if response.hovered() {
|
if response.hovered() {
|
||||||
ui.ctx().output().cursor_icon = CursorIcon::PointingHand;
|
ui.ctx().output().cursor_icon = CursorIcon::PointingHand;
|
||||||
|
@ -85,7 +89,7 @@ impl Widget for Hyperlink {
|
||||||
|
|
||||||
ui.painter().add(epaint::TextShape {
|
ui.painter().add(epaint::TextShape {
|
||||||
pos,
|
pos,
|
||||||
galley,
|
galley: text_galley.galley,
|
||||||
override_text_color: Some(color),
|
override_text_color: Some(color),
|
||||||
underline,
|
underline,
|
||||||
angle: 0.0,
|
angle: 0.0,
|
||||||
|
|
|
@ -1,9 +1,4 @@
|
||||||
use crate::*;
|
use crate::{widget_text::WidgetTextGalley, *};
|
||||||
use epaint::{
|
|
||||||
text::{LayoutJob, LayoutSection, TextFormat},
|
|
||||||
Galley,
|
|
||||||
};
|
|
||||||
use std::sync::Arc;
|
|
||||||
|
|
||||||
/// Static text.
|
/// Static text.
|
||||||
///
|
///
|
||||||
|
@ -11,135 +6,123 @@ use std::sync::Arc;
|
||||||
/// # let ui = &mut egui::Ui::__test();
|
/// # let ui = &mut egui::Ui::__test();
|
||||||
/// ui.label("Equivalent");
|
/// ui.label("Equivalent");
|
||||||
/// ui.add(egui::Label::new("Equivalent"));
|
/// ui.add(egui::Label::new("Equivalent"));
|
||||||
/// ui.add(egui::Label::new("With Options").text_color(egui::Color32::RED));
|
/// ui.add(egui::Label::new("With Options").wrap(false));
|
||||||
|
/// ui.label(egui::RichText::new("With formatting").underline());
|
||||||
/// ```
|
/// ```
|
||||||
#[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
|
text: WidgetText,
|
||||||
pub(crate) text: String,
|
wrap: Option<bool>,
|
||||||
pub(crate) wrap: Option<bool>,
|
|
||||||
pub(crate) text_style: Option<TextStyle>,
|
|
||||||
pub(crate) background_color: Color32,
|
|
||||||
pub(crate) text_color: Option<Color32>,
|
|
||||||
code: bool,
|
|
||||||
strong: bool,
|
|
||||||
weak: bool,
|
|
||||||
strikethrough: bool,
|
|
||||||
underline: bool,
|
|
||||||
italics: bool,
|
|
||||||
raised: bool,
|
|
||||||
sense: Sense,
|
sense: Sense,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Label {
|
impl Label {
|
||||||
#[allow(clippy::needless_pass_by_value)]
|
pub fn new(text: impl Into<WidgetText>) -> Self {
|
||||||
pub fn new(text: impl ToString) -> Self {
|
|
||||||
Self {
|
Self {
|
||||||
text: text.to_string(),
|
text: text.into(),
|
||||||
wrap: None,
|
wrap: None,
|
||||||
text_style: None,
|
|
||||||
background_color: Color32::TRANSPARENT,
|
|
||||||
text_color: None,
|
|
||||||
code: false,
|
|
||||||
strong: false,
|
|
||||||
weak: false,
|
|
||||||
strikethrough: false,
|
|
||||||
underline: false,
|
|
||||||
italics: false,
|
|
||||||
raised: false,
|
|
||||||
sense: Sense::focusable_noninteractive(),
|
sense: Sense::focusable_noninteractive(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn text(&self) -> &str {
|
pub fn text(&self) -> &str {
|
||||||
&self.text
|
self.text.text()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// If `true`, the text will wrap at the `max_width`.
|
/// If `true`, the text will wrap to stay within the max width of the `Ui`.
|
||||||
|
///
|
||||||
/// By default [`Self::wrap`] will be true in vertical layouts
|
/// By default [`Self::wrap`] will be true in vertical layouts
|
||||||
/// and horizontal layouts with wrapping,
|
/// and horizontal layouts with wrapping,
|
||||||
/// and false on non-wrapping horizontal layouts.
|
/// and false on non-wrapping horizontal layouts.
|
||||||
///
|
///
|
||||||
/// Note that any `\n` in the text label will always produce a new line.
|
/// Note that any `\n` in the text will always produce a new line.
|
||||||
|
#[inline]
|
||||||
pub fn wrap(mut self, wrap: bool) -> Self {
|
pub fn wrap(mut self, wrap: bool) -> Self {
|
||||||
self.wrap = Some(wrap);
|
self.wrap = Some(wrap);
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The default is [`Style::body_text_style`] (generally [`TextStyle::Body`]).
|
#[deprecated = "Replaced by Label::new(RichText::new(…).text_style(…))"]
|
||||||
pub fn text_style(mut self, text_style: TextStyle) -> Self {
|
pub fn text_style(mut self, text_style: TextStyle) -> Self {
|
||||||
self.text_style = Some(text_style);
|
self.text = self.text.text_style(text_style);
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn heading(self) -> Self {
|
#[deprecated = "Replaced by Label::new(RichText::new(…).heading())"]
|
||||||
self.text_style(TextStyle::Heading)
|
pub fn heading(mut self) -> Self {
|
||||||
|
self.text = self.text.heading();
|
||||||
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn monospace(self) -> Self {
|
#[deprecated = "Replaced by Label::new(RichText::new(…).monospace())"]
|
||||||
self.text_style(TextStyle::Monospace)
|
pub fn monospace(mut self) -> Self {
|
||||||
|
self.text = self.text.monospace();
|
||||||
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Monospace label with gray background
|
#[deprecated = "Replaced by Label::new(RichText::new(…).code())"]
|
||||||
pub fn code(mut self) -> Self {
|
pub fn code(mut self) -> Self {
|
||||||
self.code = true;
|
self.text = self.text.code();
|
||||||
self.text_style(TextStyle::Monospace)
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Extra strong text (stronger color).
|
#[deprecated = "Replaced by Label::new(RichText::new(…).strong())"]
|
||||||
pub fn strong(mut self) -> Self {
|
pub fn strong(mut self) -> Self {
|
||||||
self.strong = true;
|
self.text = self.text.strong();
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Extra weak text (fainter color).
|
#[deprecated = "Replaced by Label::new(RichText::new(…).weak())"]
|
||||||
pub fn weak(mut self) -> Self {
|
pub fn weak(mut self) -> Self {
|
||||||
self.weak = true;
|
self.text = self.text.weak();
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
/// draw a line under the text
|
#[deprecated = "Replaced by Label::new(RichText::new(…).underline())"]
|
||||||
pub fn underline(mut self) -> Self {
|
pub fn underline(mut self) -> Self {
|
||||||
self.underline = true;
|
self.text = self.text.underline();
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
/// draw a line through the text, crossing it out
|
#[deprecated = "Replaced by Label::new(RichText::new(…).strikethrough())"]
|
||||||
pub fn strikethrough(mut self) -> Self {
|
pub fn strikethrough(mut self) -> Self {
|
||||||
self.strikethrough = true;
|
self.text = self.text.strikethrough();
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
/// tilt the characters to the right.
|
#[deprecated = "Replaced by Label::new(RichText::new(…).italics())"]
|
||||||
pub fn italics(mut self) -> Self {
|
pub fn italics(mut self) -> Self {
|
||||||
self.italics = true;
|
self.text = self.text.italics();
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Smaller text
|
#[deprecated = "Replaced by Label::new(RichText::new(…).small())"]
|
||||||
pub fn small(self) -> Self {
|
pub fn small(mut self) -> Self {
|
||||||
self.text_style(TextStyle::Small)
|
self.text = self.text.small();
|
||||||
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
/// For e.g. exponents
|
#[deprecated = "Replaced by Label::new(RichText::new(…).small_raised())"]
|
||||||
pub fn small_raised(self) -> Self {
|
pub fn small_raised(mut self) -> Self {
|
||||||
self.text_style(TextStyle::Small).raised()
|
self.text = self.text.small_raised();
|
||||||
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Align text to top. Only applicable together with [`Self::small()`].
|
#[deprecated = "Replaced by Label::new(RichText::new(…).raised())"]
|
||||||
pub fn raised(mut self) -> Self {
|
pub fn raised(mut self) -> Self {
|
||||||
self.raised = true;
|
self.text = self.text.raised();
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Fill-color behind the text
|
#[deprecated = "Replaced by Label::new(RichText::new(…).background_color(…))"]
|
||||||
pub fn background_color(mut self, background_color: impl Into<Color32>) -> Self {
|
pub fn background_color(mut self, background_color: impl Into<Color32>) -> Self {
|
||||||
self.background_color = background_color.into();
|
self.text = self.text.background_color(background_color);
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[deprecated = "Replaced by Label::new(RichText::new(…).text_color())"]
|
||||||
pub fn text_color(mut self, text_color: impl Into<Color32>) -> Self {
|
pub fn text_color(mut self, text_color: impl Into<Color32>) -> Self {
|
||||||
self.text_color = Some(text_color.into());
|
self.text = self.text.color(text_color);
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -163,238 +146,119 @@ impl Label {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Label {
|
impl Label {
|
||||||
pub fn layout(&self, ui: &Ui) -> Arc<Galley> {
|
/// Do layout and position the galley in the ui, without painting it or adding widget info.
|
||||||
let max_width = ui.available_width();
|
pub fn layout_in_ui(self, ui: &mut Ui) -> (Pos2, WidgetTextGalley, Response) {
|
||||||
let line_color = self.get_text_color(ui, ui.visuals().text_color());
|
if let WidgetText::Galley(galley) = self.text {
|
||||||
self.layout_width(ui, max_width, line_color)
|
// If the user said "use this specific galley", then just use it:
|
||||||
}
|
let (rect, response) = ui.allocate_exact_size(galley.size(), self.sense);
|
||||||
|
|
||||||
/// `line_color`: used for underline and strikethrough, if any.
|
|
||||||
pub fn layout_width(&self, ui: &Ui, max_width: f32, line_color: Color32) -> Arc<Galley> {
|
|
||||||
let (halign, justify) = if ui.is_grid() {
|
|
||||||
(Align::LEFT, false) // TODO: remove special Grid hacks like these
|
|
||||||
} else {
|
|
||||||
(
|
|
||||||
ui.layout().horizontal_placement(),
|
|
||||||
ui.layout().horizontal_justify(),
|
|
||||||
)
|
|
||||||
};
|
|
||||||
self.layout_impl(ui, 0.0, max_width, 0.0, line_color, halign, justify)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[allow(clippy::too_many_arguments)]
|
|
||||||
fn layout_impl(
|
|
||||||
&self,
|
|
||||||
ui: &Ui,
|
|
||||||
leading_space: f32,
|
|
||||||
max_width: f32,
|
|
||||||
first_row_min_height: f32,
|
|
||||||
line_color: Color32,
|
|
||||||
halign: Align,
|
|
||||||
justify: bool,
|
|
||||||
) -> Arc<Galley> {
|
|
||||||
let text_style = self.text_style_or_default(ui.style());
|
|
||||||
let wrap_width = if self.should_wrap(ui) {
|
|
||||||
max_width
|
|
||||||
} else {
|
|
||||||
f32::INFINITY
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut background_color = self.background_color;
|
|
||||||
if self.code {
|
|
||||||
background_color = ui.visuals().code_bg_color;
|
|
||||||
}
|
|
||||||
let underline = if self.underline {
|
|
||||||
Stroke::new(1.0, line_color)
|
|
||||||
} else {
|
|
||||||
Stroke::none()
|
|
||||||
};
|
|
||||||
let strikethrough = if self.strikethrough {
|
|
||||||
Stroke::new(1.0, line_color)
|
|
||||||
} else {
|
|
||||||
Stroke::none()
|
|
||||||
};
|
|
||||||
|
|
||||||
let valign = if self.raised {
|
|
||||||
Align::TOP
|
|
||||||
} else {
|
|
||||||
ui.layout().vertical_align()
|
|
||||||
};
|
|
||||||
|
|
||||||
let job = LayoutJob {
|
|
||||||
text: self.text.clone(), // TODO: avoid clone
|
|
||||||
sections: vec![LayoutSection {
|
|
||||||
leading_space,
|
|
||||||
byte_range: 0..self.text.len(),
|
|
||||||
format: TextFormat {
|
|
||||||
style: text_style,
|
|
||||||
color: Color32::TEMPORARY_COLOR,
|
|
||||||
background: background_color,
|
|
||||||
italics: self.italics,
|
|
||||||
underline,
|
|
||||||
strikethrough,
|
|
||||||
valign,
|
|
||||||
},
|
|
||||||
}],
|
|
||||||
wrap_width,
|
|
||||||
first_row_min_height,
|
|
||||||
halign,
|
|
||||||
justify,
|
|
||||||
..Default::default()
|
|
||||||
};
|
|
||||||
|
|
||||||
ui.fonts().layout_job(job)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// `has_focus`: the item is selected with the keyboard, so highlight with underline.
|
|
||||||
/// `response_color`: Unless we have a special color set, use this.
|
|
||||||
pub(crate) fn paint_galley(
|
|
||||||
&self,
|
|
||||||
ui: &mut Ui,
|
|
||||||
pos: Pos2,
|
|
||||||
galley: Arc<Galley>,
|
|
||||||
has_focus: bool,
|
|
||||||
response_color: Color32,
|
|
||||||
) {
|
|
||||||
let text_color = self.get_text_color(ui, response_color);
|
|
||||||
|
|
||||||
let underline = if has_focus {
|
|
||||||
Stroke::new(1.0, text_color)
|
|
||||||
} else {
|
|
||||||
Stroke::none()
|
|
||||||
};
|
|
||||||
|
|
||||||
ui.painter().add(epaint::TextShape {
|
|
||||||
pos,
|
|
||||||
galley,
|
|
||||||
override_text_color: Some(text_color),
|
|
||||||
underline,
|
|
||||||
angle: 0.0,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/// `response_color`: Unless we have a special color set, use this.
|
|
||||||
fn get_text_color(&self, ui: &Ui, response_color: Color32) -> Color32 {
|
|
||||||
if let Some(text_color) = self.text_color {
|
|
||||||
text_color
|
|
||||||
} else if self.strong {
|
|
||||||
ui.visuals().strong_text_color()
|
|
||||||
} else if self.weak {
|
|
||||||
ui.visuals().weak_text_color()
|
|
||||||
} else {
|
|
||||||
response_color
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn font_height(&self, fonts: &epaint::text::Fonts, style: &Style) -> f32 {
|
|
||||||
let text_style = self.text_style_or_default(style);
|
|
||||||
fonts.row_height(text_style)
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: this should return a LabelLayout which has a paint method.
|
|
||||||
// We can then split Widget::Ui in two: layout + allocating space, and painting.
|
|
||||||
// this allows us to assemble labels, THEN detect interaction, THEN chose color style based on that.
|
|
||||||
// pub fn layout(self, ui: &mut ui) -> LabelLayout { }
|
|
||||||
|
|
||||||
// TODO: a paint method for painting anywhere in a ui.
|
|
||||||
// This should be the easiest method of putting text anywhere.
|
|
||||||
|
|
||||||
/// Read the text style, or get the default for the current style
|
|
||||||
pub fn text_style_or_default(&self, style: &Style) -> TextStyle {
|
|
||||||
self.text_style
|
|
||||||
.or(style.override_text_style)
|
|
||||||
.unwrap_or(style.body_text_style)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn should_wrap(&self, ui: &Ui) -> bool {
|
|
||||||
self.wrap.or(ui.style().wrap).unwrap_or_else(|| {
|
|
||||||
if let Some(grid) = ui.grid() {
|
|
||||||
grid.wrap_text()
|
|
||||||
} else {
|
|
||||||
let layout = ui.layout();
|
|
||||||
layout.is_vertical() || layout.is_horizontal() && layout.main_wrap()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Do layout and place the galley in the ui, without painting it or adding widget info.
|
|
||||||
pub(crate) fn layout_in_ui(&self, ui: &mut Ui) -> (Pos2, Arc<Galley>, Response) {
|
|
||||||
let sense = self.sense;
|
|
||||||
let max_width = ui.available_width();
|
|
||||||
|
|
||||||
if self.should_wrap(ui)
|
|
||||||
&& ui.layout().main_dir() == Direction::LeftToRight
|
|
||||||
&& ui.layout().main_wrap()
|
|
||||||
&& max_width.is_finite()
|
|
||||||
{
|
|
||||||
// On a wrapping horizontal layout we want text to start after the previous widget,
|
|
||||||
// then continue on the line below! This will take some extra work:
|
|
||||||
|
|
||||||
let cursor = ui.cursor();
|
|
||||||
let first_row_indentation = max_width - ui.available_size_before_wrap().x;
|
|
||||||
egui_assert!(first_row_indentation.is_finite());
|
|
||||||
|
|
||||||
let first_row_min_height = cursor.height();
|
|
||||||
let default_color = self.get_text_color(ui, ui.visuals().text_color());
|
|
||||||
let halign = Align::Min;
|
|
||||||
let justify = false;
|
|
||||||
let galley = self.layout_impl(
|
|
||||||
ui,
|
|
||||||
first_row_indentation,
|
|
||||||
max_width,
|
|
||||||
first_row_min_height,
|
|
||||||
default_color,
|
|
||||||
halign,
|
|
||||||
justify,
|
|
||||||
);
|
|
||||||
|
|
||||||
let pos = pos2(ui.max_rect().left(), ui.cursor().top());
|
|
||||||
assert!(!galley.rows.is_empty(), "Galleys are never empty");
|
|
||||||
// collect a response from many rows:
|
|
||||||
let rect = galley.rows[0].rect.translate(vec2(pos.x, pos.y));
|
|
||||||
let mut response = ui.allocate_rect(rect, sense);
|
|
||||||
for row in galley.rows.iter().skip(1) {
|
|
||||||
let rect = row.rect.translate(vec2(pos.x, pos.y));
|
|
||||||
response |= ui.allocate_rect(rect, sense);
|
|
||||||
}
|
|
||||||
(pos, galley, response)
|
|
||||||
} else {
|
|
||||||
let galley = self.layout(ui);
|
|
||||||
let (rect, response) = ui.allocate_exact_size(galley.size(), sense);
|
|
||||||
let pos = match galley.job.halign {
|
let pos = match galley.job.halign {
|
||||||
Align::LEFT => rect.left_top(),
|
Align::LEFT => rect.left_top(),
|
||||||
Align::Center => rect.center_top(),
|
Align::Center => rect.center_top(),
|
||||||
Align::RIGHT => rect.right_top(),
|
Align::RIGHT => rect.right_top(),
|
||||||
};
|
};
|
||||||
(pos, galley, response)
|
let text_galley = WidgetTextGalley {
|
||||||
|
galley,
|
||||||
|
galley_has_color: true,
|
||||||
|
};
|
||||||
|
return (pos, text_galley, response);
|
||||||
|
}
|
||||||
|
|
||||||
|
let valign = ui.layout().vertical_align();
|
||||||
|
let mut text_job = self.text.into_text_job(ui.style(), TextStyle::Body, valign);
|
||||||
|
|
||||||
|
let should_wrap = self.wrap.unwrap_or_else(|| ui.wrap_text());
|
||||||
|
let available_width = ui.available_width();
|
||||||
|
|
||||||
|
if should_wrap
|
||||||
|
&& ui.layout().main_dir() == Direction::LeftToRight
|
||||||
|
&& ui.layout().main_wrap()
|
||||||
|
&& available_width.is_finite()
|
||||||
|
{
|
||||||
|
// On a wrapping horizontal layout we want text to start after the previous widget,
|
||||||
|
// then continue on the line below! This will take some extra work:
|
||||||
|
|
||||||
|
let cursor = ui.cursor();
|
||||||
|
let first_row_indentation = available_width - ui.available_size_before_wrap().x;
|
||||||
|
egui_assert!(first_row_indentation.is_finite());
|
||||||
|
|
||||||
|
text_job.job.wrap_width = available_width;
|
||||||
|
text_job.job.first_row_min_height = cursor.height();
|
||||||
|
text_job.job.halign = Align::Min;
|
||||||
|
text_job.job.justify = false;
|
||||||
|
if let Some(first_section) = text_job.job.sections.first_mut() {
|
||||||
|
first_section.leading_space = first_row_indentation;
|
||||||
|
}
|
||||||
|
let text_galley = text_job.into_galley(ui.fonts());
|
||||||
|
|
||||||
|
let pos = pos2(ui.max_rect().left(), ui.cursor().top());
|
||||||
|
assert!(
|
||||||
|
!text_galley.galley.rows.is_empty(),
|
||||||
|
"Galleys are never empty"
|
||||||
|
);
|
||||||
|
// collect a response from many rows:
|
||||||
|
let rect = text_galley.galley.rows[0]
|
||||||
|
.rect
|
||||||
|
.translate(vec2(pos.x, pos.y));
|
||||||
|
let mut response = ui.allocate_rect(rect, self.sense);
|
||||||
|
for row in text_galley.galley.rows.iter().skip(1) {
|
||||||
|
let rect = row.rect.translate(vec2(pos.x, pos.y));
|
||||||
|
response |= ui.allocate_rect(rect, self.sense);
|
||||||
|
}
|
||||||
|
(pos, text_galley, response)
|
||||||
|
} else {
|
||||||
|
if should_wrap {
|
||||||
|
text_job.job.wrap_width = available_width;
|
||||||
|
} else {
|
||||||
|
text_job.job.wrap_width = f32::INFINITY;
|
||||||
|
};
|
||||||
|
|
||||||
|
if ui.is_grid() {
|
||||||
|
// TODO: remove special Grid hacks like these
|
||||||
|
text_job.job.halign = Align::LEFT;
|
||||||
|
text_job.job.justify = false;
|
||||||
|
} else {
|
||||||
|
text_job.job.halign = ui.layout().horizontal_placement();
|
||||||
|
text_job.job.justify = ui.layout().horizontal_justify();
|
||||||
|
};
|
||||||
|
|
||||||
|
let text_galley = text_job.into_galley(ui.fonts());
|
||||||
|
let (rect, response) = ui.allocate_exact_size(text_galley.size(), self.sense);
|
||||||
|
let pos = match text_galley.galley.job.halign {
|
||||||
|
Align::LEFT => rect.left_top(),
|
||||||
|
Align::Center => rect.center_top(),
|
||||||
|
Align::RIGHT => rect.right_top(),
|
||||||
|
};
|
||||||
|
(pos, text_galley, response)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Widget for Label {
|
impl Widget for Label {
|
||||||
fn ui(self, ui: &mut Ui) -> Response {
|
fn ui(self, ui: &mut Ui) -> Response {
|
||||||
let (pos, galley, response) = self.layout_in_ui(ui);
|
let (pos, text_galley, response) = self.layout_in_ui(ui);
|
||||||
response.widget_info(|| WidgetInfo::labeled(WidgetType::Label, galley.text()));
|
response.widget_info(|| WidgetInfo::labeled(WidgetType::Label, text_galley.text()));
|
||||||
let response_color = ui.style().interact(&response).text_color();
|
let response_color = ui.style().interact(&response).text_color();
|
||||||
self.paint_galley(ui, pos, galley, response.has_focus(), response_color);
|
|
||||||
|
let underline = if response.has_focus() {
|
||||||
|
Stroke::new(1.0, response_color)
|
||||||
|
} else {
|
||||||
|
Stroke::none()
|
||||||
|
};
|
||||||
|
|
||||||
|
let override_text_color = if text_galley.galley_has_color {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
Some(response_color)
|
||||||
|
};
|
||||||
|
|
||||||
|
ui.painter().add(epaint::TextShape {
|
||||||
|
pos,
|
||||||
|
galley: text_galley.galley,
|
||||||
|
override_text_color,
|
||||||
|
underline,
|
||||||
|
angle: 0.0,
|
||||||
|
});
|
||||||
|
|
||||||
response
|
response
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<&str> for Label {
|
|
||||||
fn from(s: &str) -> Label {
|
|
||||||
Label::new(s)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<&String> for Label {
|
|
||||||
fn from(s: &String) -> Label {
|
|
||||||
Label::new(s)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<String> for Label {
|
|
||||||
fn from(s: String) -> Label {
|
|
||||||
Label::new(s)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
use crate::*;
|
use crate::*;
|
||||||
|
|
||||||
enum ProgressBarText {
|
enum ProgressBarText {
|
||||||
Custom(String),
|
Custom(WidgetText),
|
||||||
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<WidgetText>) -> 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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -124,17 +123,19 @@ impl Widget for ProgressBar {
|
||||||
|
|
||||||
if let Some(text_kind) = text {
|
if let Some(text_kind) = text {
|
||||||
let text = match text_kind {
|
let text = match text_kind {
|
||||||
ProgressBarText::Custom(string) => string,
|
ProgressBarText::Custom(text) => text,
|
||||||
ProgressBarText::Percentage => format!("{}%", (progress * 100.0) as usize),
|
ProgressBarText::Percentage => format!("{}%", (progress * 100.0) as usize).into(),
|
||||||
};
|
};
|
||||||
ui.painter().sub_region(outer_rect).text(
|
let galley = text.into_galley(ui, Some(false), f32::INFINITY, TextStyle::Button);
|
||||||
outer_rect.left_center() + vec2(ui.spacing().item_spacing.x, 0.0),
|
let text_pos = outer_rect.left_center() - Vec2::new(0.0, galley.size().y / 2.0)
|
||||||
Align2::LEFT_CENTER,
|
+ vec2(ui.spacing().item_spacing.x, 0.0);
|
||||||
text,
|
let text_color = visuals
|
||||||
TextStyle::Button,
|
.override_text_color
|
||||||
visuals
|
.unwrap_or(visuals.selection.stroke.color);
|
||||||
.override_text_color
|
galley.paint_with_fallback_color(
|
||||||
.unwrap_or(visuals.selection.stroke.color),
|
&ui.painter().sub_region(outer_rect),
|
||||||
|
text_pos,
|
||||||
|
text_color,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -21,64 +21,46 @@ 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);`"]
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct SelectableLabel {
|
pub struct SelectableLabel {
|
||||||
selected: bool,
|
selected: bool,
|
||||||
text: String,
|
text: WidgetText,
|
||||||
text_style: Option<TextStyle>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SelectableLabel {
|
impl SelectableLabel {
|
||||||
#[allow(clippy::needless_pass_by_value)]
|
pub fn new(selected: bool, text: impl Into<WidgetText>) -> Self {
|
||||||
pub fn new(selected: bool, text: impl ToString) -> Self {
|
|
||||||
Self {
|
Self {
|
||||||
selected,
|
selected,
|
||||||
text: text.to_string(),
|
text: text.into(),
|
||||||
text_style: None,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[deprecated = "Replaced by: Button::new(RichText::new(text).text_style(…))"]
|
||||||
pub fn text_style(mut self, text_style: TextStyle) -> Self {
|
pub fn text_style(mut self, text_style: TextStyle) -> Self {
|
||||||
self.text_style = Some(text_style);
|
self.text = self.text.text_style(text_style);
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Widget for SelectableLabel {
|
impl Widget for SelectableLabel {
|
||||||
fn ui(self, ui: &mut Ui) -> Response {
|
fn ui(self, ui: &mut Ui) -> Response {
|
||||||
let Self {
|
let Self { selected, text } = self;
|
||||||
selected,
|
|
||||||
text,
|
|
||||||
text_style,
|
|
||||||
} = self;
|
|
||||||
|
|
||||||
let text_style = text_style
|
|
||||||
.or(ui.style().override_text_style)
|
|
||||||
.unwrap_or(TextStyle::Button);
|
|
||||||
|
|
||||||
let button_padding = ui.spacing().button_padding;
|
let button_padding = ui.spacing().button_padding;
|
||||||
let total_extra = button_padding + button_padding;
|
let total_extra = button_padding + button_padding;
|
||||||
|
|
||||||
let wrap_width = if ui.wrap_text() {
|
let wrap_width = ui.available_width() - total_extra.x;
|
||||||
ui.available_width() - total_extra.x
|
let text = text.into_galley(ui, None, wrap_width, TextStyle::Button);
|
||||||
} else {
|
|
||||||
f32::INFINITY
|
|
||||||
};
|
|
||||||
|
|
||||||
let galley = ui
|
let mut desired_size = total_extra + text.size();
|
||||||
.fonts()
|
|
||||||
.layout_delayed_color(text, text_style, wrap_width);
|
|
||||||
|
|
||||||
let mut desired_size = total_extra + galley.size();
|
|
||||||
desired_size.y = desired_size.y.at_least(ui.spacing().interact_size.y);
|
desired_size.y = desired_size.y.at_least(ui.spacing().interact_size.y);
|
||||||
let (rect, response) = ui.allocate_at_least(desired_size, Sense::click());
|
let (rect, response) = ui.allocate_at_least(desired_size, Sense::click());
|
||||||
response.widget_info(|| {
|
response.widget_info(|| {
|
||||||
WidgetInfo::selected(WidgetType::SelectableLabel, selected, galley.text())
|
WidgetInfo::selected(WidgetType::SelectableLabel, selected, text.text())
|
||||||
});
|
});
|
||||||
|
|
||||||
let text_pos = ui
|
let text_pos = ui
|
||||||
.layout()
|
.layout()
|
||||||
.align_size_within_rect(galley.size(), rect.shrink2(button_padding))
|
.align_size_within_rect(text.size(), rect.shrink2(button_padding))
|
||||||
.min;
|
.min;
|
||||||
|
|
||||||
let visuals = ui.style().interact_selectable(&response, selected);
|
let visuals = ui.style().interact_selectable(&response, selected);
|
||||||
|
@ -91,12 +73,7 @@ impl Widget for SelectableLabel {
|
||||||
.rect(rect, corner_radius, visuals.bg_fill, visuals.bg_stroke);
|
.rect(rect, corner_radius, visuals.bg_fill, visuals.bg_stroke);
|
||||||
}
|
}
|
||||||
|
|
||||||
let text_color = ui
|
text.paint_with_visuals(ui.painter(), text_pos, &visuals);
|
||||||
.style()
|
|
||||||
.visuals
|
|
||||||
.override_text_color
|
|
||||||
.unwrap_or_else(|| visuals.text_color());
|
|
||||||
ui.painter().galley_with_color(text_pos, galley, text_color);
|
|
||||||
response
|
response
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,8 +1,9 @@
|
||||||
#![allow(clippy::needless_pass_by_value)] // False positives with `impl ToString`
|
#![allow(clippy::needless_pass_by_value)] // False positives with `impl ToString`
|
||||||
|
|
||||||
use crate::{widgets::Label, *};
|
|
||||||
use std::ops::RangeInclusive;
|
use std::ops::RangeInclusive;
|
||||||
|
|
||||||
|
use crate::*;
|
||||||
|
|
||||||
// ----------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------
|
||||||
|
|
||||||
/// Combined into one function (rather than two) to make it easier
|
/// Combined into one function (rather than two) to make it easier
|
||||||
|
@ -356,7 +357,8 @@ impl<'a> Slider<'a> {
|
||||||
fn label_ui(&mut self, ui: &mut Ui) {
|
fn label_ui(&mut self, ui: &mut Ui) {
|
||||||
if !self.text.is_empty() {
|
if !self.text.is_empty() {
|
||||||
let text_color = self.text_color.unwrap_or_else(|| ui.visuals().text_color());
|
let text_color = self.text_color.unwrap_or_else(|| ui.visuals().text_color());
|
||||||
ui.add(Label::new(&self.text).wrap(false).text_color(text_color));
|
let text = RichText::new(&self.text).color(text_color);
|
||||||
|
ui.add(Label::new(text).wrap(false));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -45,7 +45,7 @@ use super::{CCursorRange, CursorRange, TextEditOutput, TextEditState};
|
||||||
#[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> {
|
pub struct TextEdit<'t> {
|
||||||
text: &'t mut dyn TextBuffer,
|
text: &'t mut dyn TextBuffer,
|
||||||
hint_text: String,
|
hint_text: WidgetText,
|
||||||
id: Option<Id>,
|
id: Option<Id>,
|
||||||
id_source: Option<Id>,
|
id_source: Option<Id>,
|
||||||
text_style: Option<TextStyle>,
|
text_style: Option<TextStyle>,
|
||||||
|
@ -127,9 +127,8 @@ impl<'t> TextEdit<'t> {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 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<WidgetText>) -> 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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -512,12 +511,12 @@ impl<'t> TextEdit<'t> {
|
||||||
|
|
||||||
if text.as_ref().is_empty() && !hint_text.is_empty() {
|
if text.as_ref().is_empty() && !hint_text.is_empty() {
|
||||||
let hint_text_color = ui.visuals().weak_text_color();
|
let hint_text_color = ui.visuals().weak_text_color();
|
||||||
let galley = ui.fonts().layout_job(if multiline {
|
let galley = if multiline {
|
||||||
LayoutJob::simple(hint_text, text_style, hint_text_color, desired_size.x)
|
hint_text.into_galley(ui, Some(true), desired_size.x, text_style)
|
||||||
} else {
|
} else {
|
||||||
LayoutJob::simple_singleline(hint_text, text_style, hint_text_color)
|
hint_text.into_galley(ui, Some(false), f32::INFINITY, text_style)
|
||||||
});
|
};
|
||||||
painter.galley(response.rect.min, galley);
|
galley.paint_with_fallback_color(&painter, response.rect.min, hint_text_color);
|
||||||
}
|
}
|
||||||
|
|
||||||
if ui.memory().has_focus(id) {
|
if ui.memory().has_focus(id) {
|
||||||
|
|
|
@ -42,13 +42,10 @@ impl super::View for FontBook {
|
||||||
ui.horizontal_wrapped(|ui| {
|
ui.horizontal_wrapped(|ui| {
|
||||||
ui.spacing_mut().item_spacing.x = 0.0;
|
ui.spacing_mut().item_spacing.x = 0.0;
|
||||||
ui.label("You can add more characters by installing additional fonts with ");
|
ui.label("You can add more characters by installing additional fonts with ");
|
||||||
ui.add(
|
ui.add(egui::Hyperlink::from_label_and_url(
|
||||||
egui::Hyperlink::from_label_and_url(
|
egui::RichText::new("Context::set_fonts").text_style(egui::TextStyle::Monospace),
|
||||||
"Context::set_fonts",
|
"https://docs.rs/egui/latest/egui/struct.Context.html#method.set_fonts",
|
||||||
"https://docs.rs/egui/latest/egui/struct.Context.html#method.set_fonts",
|
));
|
||||||
)
|
|
||||||
.text_style(egui::TextStyle::Monospace),
|
|
||||||
);
|
|
||||||
ui.label(".");
|
ui.label(".");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -90,10 +87,13 @@ 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(
|
||||||
|
egui::RichText::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.label(egui::RichText::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));
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -142,7 +142,7 @@ impl Widgets {
|
||||||
// Trick so we don't have to add spaces in the text below:
|
// Trick so we don't have to add spaces in the text below:
|
||||||
ui.spacing_mut().item_spacing.x = ui.fonts()[TextStyle::Body].glyph_width(' ');
|
ui.spacing_mut().item_spacing.x = ui.fonts()[TextStyle::Body].glyph_width(' ');
|
||||||
|
|
||||||
ui.add(Label::new("Text can have").text_color(Color32::from_rgb(110, 255, 110)));
|
ui.label(RichText::new("Text can have").color(Color32::from_rgb(110, 255, 110)));
|
||||||
ui.colored_label(Color32::from_rgb(128, 140, 255), "color"); // Shortcut version
|
ui.colored_label(Color32::from_rgb(128, 140, 255), "color"); // Shortcut version
|
||||||
ui.label("and tooltips.").on_hover_text(
|
ui.label("and tooltips.").on_hover_text(
|
||||||
"This is a multiline tooltip that demonstrates that you can easily add tooltips to any element.\nThis is the second line.\nThis is the third.",
|
"This is a multiline tooltip that demonstrates that you can easily add tooltips to any element.\nThis is the second line.\nThis is the third.",
|
||||||
|
@ -365,7 +365,7 @@ impl SubTree {
|
||||||
) -> Action {
|
) -> Action {
|
||||||
if depth > 0
|
if depth > 0
|
||||||
&& ui
|
&& ui
|
||||||
.add(Button::new("delete").text_color(Color32::RED))
|
.button(RichText::new("delete").color(Color32::RED))
|
||||||
.clicked()
|
.clicked()
|
||||||
{
|
{
|
||||||
return Action::Delete;
|
return Action::Delete;
|
||||||
|
@ -565,12 +565,7 @@ fn text_layout_ui(ui: &mut egui::Ui) {
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
job.wrap_width = ui.available_width();
|
ui.label(job);
|
||||||
|
|
||||||
let galley = ui.fonts().layout_job(job);
|
|
||||||
|
|
||||||
let (response, painter) = ui.allocate_painter(galley.size(), Sense::hover());
|
|
||||||
painter.add(Shape::galley(response.rect.min, galley));
|
|
||||||
|
|
||||||
ui.vertical_centered(|ui| {
|
ui.vertical_centered(|ui| {
|
||||||
ui.add(crate::__egui_github_link_file_line!());
|
ui.add(crate::__egui_github_link_file_line!());
|
||||||
|
|
|
@ -465,7 +465,7 @@ fn lorem_ipsum(ui: &mut egui::Ui, text: &str) {
|
||||||
ui.with_layout(
|
ui.with_layout(
|
||||||
egui::Layout::top_down(egui::Align::LEFT).with_cross_justify(true),
|
egui::Layout::top_down(egui::Align::LEFT).with_cross_justify(true),
|
||||||
|ui| {
|
|ui| {
|
||||||
ui.add(egui::Label::new(text).weak());
|
ui.label(egui::RichText::new(text).weak());
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -84,7 +84,7 @@ fn lorem_ipsum(ui: &mut egui::Ui) {
|
||||||
ui.with_layout(
|
ui.with_layout(
|
||||||
egui::Layout::top_down(egui::Align::LEFT).with_cross_justify(true),
|
egui::Layout::top_down(egui::Align::LEFT).with_cross_justify(true),
|
||||||
|ui| {
|
|ui| {
|
||||||
ui.add(egui::Label::new(crate::LOREM_IPSUM_LONG).small().weak());
|
ui.label(egui::RichText::new(crate::LOREM_IPSUM_LONG).small().weak());
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -116,9 +116,9 @@ 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(
|
ui.colored_label(
|
||||||
egui::Label::new(if error.is_empty() { "Error" } else { error })
|
egui::Color32::RED,
|
||||||
.text_color(egui::Color32::RED),
|
if error.is_empty() { "Error" } else { error },
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -39,10 +39,10 @@ pub fn item_ui(ui: &mut Ui, item: easy_mark::Item<'_>) {
|
||||||
}
|
}
|
||||||
|
|
||||||
easy_mark::Item::Text(style, text) => {
|
easy_mark::Item::Text(style, text) => {
|
||||||
ui.add(label_from_style(text, &style));
|
ui.label(rich_text_from_style(text, &style));
|
||||||
}
|
}
|
||||||
easy_mark::Item::Hyperlink(style, text, url) => {
|
easy_mark::Item::Hyperlink(style, text, url) => {
|
||||||
let label = label_from_style(text, &style);
|
let label = rich_text_from_style(text, &style);
|
||||||
ui.add(Hyperlink::from_label_and_url(label, url));
|
ui.add(Hyperlink::from_label_and_url(label, url));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -87,7 +87,7 @@ pub fn item_ui(ui: &mut Ui, item: easy_mark::Item<'_>) {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
fn label_from_style(text: &str, style: &easy_mark::Style) -> Label {
|
fn rich_text_from_style(text: &str, style: &easy_mark::Style) -> RichText {
|
||||||
let easy_mark::Style {
|
let easy_mark::Style {
|
||||||
heading,
|
heading,
|
||||||
quoted,
|
quoted,
|
||||||
|
@ -102,34 +102,34 @@ 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 rich_text = RichText::new(text);
|
||||||
if heading && !small {
|
if heading && !small {
|
||||||
label = label.heading().strong();
|
rich_text = rich_text.heading().strong();
|
||||||
}
|
}
|
||||||
if small && !heading {
|
if small && !heading {
|
||||||
label = label.small();
|
rich_text = rich_text.small();
|
||||||
}
|
}
|
||||||
if code {
|
if code {
|
||||||
label = label.code();
|
rich_text = rich_text.code();
|
||||||
}
|
}
|
||||||
if strong {
|
if strong {
|
||||||
label = label.strong();
|
rich_text = rich_text.strong();
|
||||||
} else if quoted {
|
} else if quoted {
|
||||||
label = label.weak();
|
rich_text = rich_text.weak();
|
||||||
}
|
}
|
||||||
if underline {
|
if underline {
|
||||||
label = label.underline();
|
rich_text = rich_text.underline();
|
||||||
}
|
}
|
||||||
if strikethrough {
|
if strikethrough {
|
||||||
label = label.strikethrough();
|
rich_text = rich_text.strikethrough();
|
||||||
}
|
}
|
||||||
if italics {
|
if italics {
|
||||||
label = label.italics();
|
rich_text = rich_text.italics();
|
||||||
}
|
}
|
||||||
if raised {
|
if raised {
|
||||||
label = label.raised();
|
rich_text = rich_text.raised();
|
||||||
}
|
}
|
||||||
label
|
rich_text
|
||||||
}
|
}
|
||||||
|
|
||||||
fn bullet_point(ui: &mut Ui, width: f32) -> Response {
|
fn bullet_point(ui: &mut Ui, width: f32) -> Response {
|
||||||
|
|
|
@ -105,7 +105,10 @@ macro_rules! __egui_github_link_file {
|
||||||
crate::__egui_github_link_file!("(source code)")
|
crate::__egui_github_link_file!("(source code)")
|
||||||
};
|
};
|
||||||
($label: expr) => {
|
($label: expr) => {
|
||||||
egui::github_link_file!("https://github.com/emilk/egui/blob/master/", $label).small()
|
egui::github_link_file!(
|
||||||
|
"https://github.com/emilk/egui/blob/master/",
|
||||||
|
egui::RichText::new($label).small()
|
||||||
|
)
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -117,7 +120,10 @@ macro_rules! __egui_github_link_file_line {
|
||||||
crate::__egui_github_link_file_line!("(source code)")
|
crate::__egui_github_link_file_line!("(source code)")
|
||||||
};
|
};
|
||||||
($label: expr) => {
|
($label: expr) => {
|
||||||
egui::github_link_file_line!("https://github.com/emilk/egui/blob/master/", $label).small()
|
egui::github_link_file_line!(
|
||||||
|
"https://github.com/emilk/egui/blob/master/",
|
||||||
|
egui::RichText::new($label).small()
|
||||||
|
)
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -237,5 +237,5 @@ fn clock_button(ui: &mut egui::Ui, seconds_since_midnight: f64) -> egui::Respons
|
||||||
(time % 1.0 * 100.0).floor()
|
(time % 1.0 * 100.0).floor()
|
||||||
);
|
);
|
||||||
|
|
||||||
ui.add(egui::Button::new(time).text_style(egui::TextStyle::Monospace))
|
ui.button(egui::RichText::new(time).monospace())
|
||||||
}
|
}
|
||||||
|
|
|
@ -121,7 +121,7 @@ impl EguiGlium {
|
||||||
&self.egui_ctx
|
&self.egui_ctx
|
||||||
}
|
}
|
||||||
|
|
||||||
/// useful for calling e.g. [`crate::Painter::register_glium_texture`].
|
/// useful for calling e.g. [`crate::Painter::alloc_user_texture`].
|
||||||
pub fn painter_mut(&mut self) -> &mut crate::Painter {
|
pub fn painter_mut(&mut self) -> &mut crate::Painter {
|
||||||
&mut self.painter
|
&mut self.painter
|
||||||
}
|
}
|
||||||
|
|
|
@ -37,6 +37,9 @@ use emath::*;
|
||||||
/// },
|
/// },
|
||||||
/// );
|
/// );
|
||||||
/// ```
|
/// ```
|
||||||
|
///
|
||||||
|
/// As you can see, constructing a `LayoutJob` is currently a lot of work.
|
||||||
|
/// It would be nice to have a helper macro for it!
|
||||||
#[derive(Clone, Debug, PartialEq)]
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
|
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
|
||||||
pub struct LayoutJob {
|
pub struct LayoutJob {
|
||||||
|
@ -118,6 +121,21 @@ impl LayoutJob {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn single_section(text: String, format: TextFormat) -> Self {
|
||||||
|
Self {
|
||||||
|
sections: vec![LayoutSection {
|
||||||
|
leading_space: 0.0,
|
||||||
|
byte_range: 0..text.len(),
|
||||||
|
format,
|
||||||
|
}],
|
||||||
|
text,
|
||||||
|
wrap_width: f32::INFINITY,
|
||||||
|
break_on_newline: true,
|
||||||
|
..Default::default()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub fn is_empty(&self) -> bool {
|
pub fn is_empty(&self) -> bool {
|
||||||
self.sections.is_empty()
|
self.sections.is_empty()
|
||||||
|
@ -134,6 +152,15 @@ impl LayoutJob {
|
||||||
format,
|
format,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The height of the tallest used font in the job.
|
||||||
|
pub fn font_height(&self, fonts: &crate::Fonts) -> f32 {
|
||||||
|
let mut max_height = 0.0_f32;
|
||||||
|
for section in &self.sections {
|
||||||
|
max_height = max_height.max(fonts.row_height(section.format.style));
|
||||||
|
}
|
||||||
|
max_height
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl std::hash::Hash for LayoutJob {
|
impl std::hash::Hash for LayoutJob {
|
||||||
|
|
Loading…
Reference in a new issue