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 ⭐
|
||||
* 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)).
|
||||
* Most widgets containing text (`Label`, `Button` etc) now supports rich text ([#855](https://github.com/emilk/egui/pull/855)).
|
||||
|
||||
### 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)).
|
||||
* `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 🙏
|
||||
* [mankinskin](https://github.com/mankinskin) ([#543](https://github.com/emilk/egui/pull/543))
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
use std::hash::Hash;
|
||||
|
||||
use crate::{widgets::Label, *};
|
||||
use crate::*;
|
||||
use epaint::{Shape, TextStyle};
|
||||
|
||||
#[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()"]
|
||||
pub struct CollapsingHeader {
|
||||
label: Label,
|
||||
text: WidgetText,
|
||||
default_open: bool,
|
||||
id_source: Id,
|
||||
enabled: bool,
|
||||
|
@ -157,11 +157,11 @@ impl CollapsingHeader {
|
|||
/// If the label is unique and static this is fine,
|
||||
/// 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`].
|
||||
pub fn new(label: impl ToString) -> Self {
|
||||
let label = Label::new(label).wrap(false);
|
||||
let id_source = Id::new(label.text());
|
||||
pub fn new(text: impl Into<WidgetText>) -> Self {
|
||||
let text = text.into();
|
||||
let id_source = Id::new(text.text());
|
||||
Self {
|
||||
label,
|
||||
text,
|
||||
default_open: false,
|
||||
id_source,
|
||||
enabled: true,
|
||||
|
@ -185,10 +185,9 @@ impl CollapsingHeader {
|
|||
self
|
||||
}
|
||||
|
||||
/// By default, the `CollapsingHeader` text style is `TextStyle::Button`.
|
||||
/// Call `.text_style(style)` to change this.
|
||||
#[deprecated = "Replaced by: CollapsingHeader::new(RichText::new(text).text_style(…))"]
|
||||
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
|
||||
}
|
||||
|
||||
|
@ -252,7 +251,7 @@ impl CollapsingHeader {
|
|||
"Horizontal collapsing is unimplemented"
|
||||
);
|
||||
let Self {
|
||||
mut label,
|
||||
text,
|
||||
default_open,
|
||||
id_source,
|
||||
enabled: _,
|
||||
|
@ -261,11 +260,6 @@ impl CollapsingHeader {
|
|||
show_background: _,
|
||||
} = 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.
|
||||
|
||||
let id = ui.make_persistent_id(id_source);
|
||||
|
@ -273,23 +267,24 @@ impl CollapsingHeader {
|
|||
|
||||
let available = ui.available_rect_before_wrap();
|
||||
let text_pos = available.min + vec2(ui.spacing().indent, 0.0);
|
||||
let galley =
|
||||
label.layout_width(ui, available.right() - text_pos.x, Color32::TEMPORARY_COLOR);
|
||||
let text_max_x = text_pos.x + galley.size().x;
|
||||
let wrap_width = available.right() - text_pos.x;
|
||||
let wrap = Some(false);
|
||||
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();
|
||||
if ui.visuals().collapsing_header_frame {
|
||||
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);
|
||||
let (_, rect) = ui.allocate_space(desired_size);
|
||||
|
||||
let mut header_response = ui.interact(rect, id, Sense::click());
|
||||
let text_pos = pos2(
|
||||
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);
|
||||
|
@ -298,16 +293,11 @@ impl CollapsingHeader {
|
|||
header_response.mark_changed();
|
||||
}
|
||||
header_response
|
||||
.widget_info(|| WidgetInfo::labeled(WidgetType::CollapsingHeader, galley.text()));
|
||||
.widget_info(|| WidgetInfo::labeled(WidgetType::CollapsingHeader, text.text()));
|
||||
|
||||
let visuals = ui
|
||||
.style()
|
||||
.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 {
|
||||
ui.painter().add(epaint::RectShape {
|
||||
|
@ -343,7 +333,7 @@ impl CollapsingHeader {
|
|||
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 {
|
||||
id,
|
||||
|
|
|
@ -1,8 +1,6 @@
|
|||
use crate::{style::WidgetVisuals, *};
|
||||
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.
|
||||
///
|
||||
/// ```
|
||||
|
@ -22,14 +20,14 @@ use epaint::Shape;
|
|||
#[must_use = "You should call .show*"]
|
||||
pub struct ComboBox {
|
||||
id_source: Id,
|
||||
label: Option<Label>,
|
||||
selected_text: String,
|
||||
label: Option<WidgetText>,
|
||||
selected_text: WidgetText,
|
||||
width: Option<f32>,
|
||||
}
|
||||
|
||||
impl ComboBox {
|
||||
/// 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();
|
||||
Self {
|
||||
id_source: Id::new(label.text()),
|
||||
|
@ -56,9 +54,8 @@ impl ComboBox {
|
|||
}
|
||||
|
||||
/// What we show as the currently selected value
|
||||
#[allow(clippy::needless_pass_by_value)]
|
||||
pub fn selected_text(mut self, selected_text: impl ToString) -> Self {
|
||||
self.selected_text = selected_text.to_string();
|
||||
pub fn selected_text(mut self, selected_text: impl Into<WidgetText>) -> Self {
|
||||
self.selected_text = selected_text.into();
|
||||
self
|
||||
}
|
||||
|
||||
|
@ -95,7 +92,7 @@ impl ComboBox {
|
|||
if let Some(label) = label {
|
||||
ir.response
|
||||
.widget_info(|| WidgetInfo::labeled(WidgetType::ComboBox, label.text()));
|
||||
ir.response |= ui.add(label);
|
||||
ir.response |= ui.label(label);
|
||||
} else {
|
||||
ir.response
|
||||
.widget_info(|| WidgetInfo::labeled(WidgetType::ComboBox, ""));
|
||||
|
@ -151,11 +148,10 @@ impl ComboBox {
|
|||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::needless_pass_by_value)]
|
||||
fn combo_box_dyn<'c, R>(
|
||||
ui: &mut Ui,
|
||||
button_id: Id,
|
||||
selected: impl ToString,
|
||||
selected_text: WidgetText,
|
||||
menu_contents: Box<dyn FnOnce(&mut Ui) -> R + 'c>,
|
||||
) -> InnerResponse<Option<R>> {
|
||||
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 icon_size = Vec2::splat(ui.spacing().icon_width);
|
||||
|
||||
let galley =
|
||||
ui.fonts()
|
||||
.layout_delayed_color(selected.to_string(), TextStyle::Button, f32::INFINITY);
|
||||
let galley = selected_text.into_galley(ui, Some(false), f32::INFINITY, TextStyle::Button);
|
||||
|
||||
let width = galley.size().x + ui.spacing().item_spacing.x + icon_size.x;
|
||||
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);
|
||||
|
||||
let text_rect = Align2::LEFT_CENTER.align_size_within_rect(galley.size(), rect);
|
||||
ui.painter()
|
||||
.galley_with_color(text_rect.min, galley, visuals.text_color());
|
||||
galley.paint_with_visuals(ui.painter(), text_rect.min, visuals);
|
||||
});
|
||||
|
||||
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");
|
||||
/// }
|
||||
/// ```
|
||||
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| {
|
||||
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.
|
||||
|
||||
use crate::{widgets::*, *};
|
||||
use crate::{widget_text::WidgetTextGalley, *};
|
||||
use epaint::*;
|
||||
|
||||
use super::*;
|
||||
|
@ -23,7 +23,7 @@ use super::*;
|
|||
/// });
|
||||
#[must_use = "You should call .show()"]
|
||||
pub struct Window<'open> {
|
||||
title_label: Label,
|
||||
title: WidgetText,
|
||||
open: Option<&'open mut bool>,
|
||||
area: Area,
|
||||
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.
|
||||
/// 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.
|
||||
#[allow(clippy::needless_pass_by_value)]
|
||||
pub fn new(title: impl ToString) -> Self {
|
||||
let title = title.to_string();
|
||||
let area = Area::new(&title);
|
||||
let title_label = Label::new(title).text_style(TextStyle::Heading).wrap(false);
|
||||
pub fn new(title: impl Into<WidgetText>) -> Self {
|
||||
let title = title.into().fallback_text_style(TextStyle::Heading);
|
||||
let area = Area::new(title.text());
|
||||
Self {
|
||||
title_label,
|
||||
title,
|
||||
open: None,
|
||||
area,
|
||||
frame: None,
|
||||
|
@ -250,7 +248,7 @@ impl<'open> Window<'open> {
|
|||
add_contents: Box<dyn FnOnce(&mut Ui) -> R + 'c>,
|
||||
) -> Option<InnerResponse<Option<R>>> {
|
||||
let Window {
|
||||
title_label,
|
||||
title,
|
||||
open,
|
||||
area,
|
||||
frame,
|
||||
|
@ -299,7 +297,7 @@ impl<'open> Window<'open> {
|
|||
.and_then(|window_interaction| {
|
||||
// Calculate roughly how much larger the window size is compared to the inner rect
|
||||
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 {
|
||||
0.0
|
||||
};
|
||||
|
@ -336,7 +334,7 @@ impl<'open> Window<'open> {
|
|||
let title_bar = if with_title_bar {
|
||||
let title_bar = show_title_bar(
|
||||
&mut frame.content_ui,
|
||||
title_label,
|
||||
title,
|
||||
show_close_button,
|
||||
collapsing_id,
|
||||
&mut collapsing,
|
||||
|
@ -745,22 +743,21 @@ fn paint_frame_interaction(
|
|||
|
||||
struct TitleBar {
|
||||
id: Id,
|
||||
title_label: Label,
|
||||
title_galley: std::sync::Arc<Galley>,
|
||||
title_galley: WidgetTextGalley,
|
||||
min_rect: Rect,
|
||||
rect: Rect,
|
||||
}
|
||||
|
||||
fn show_title_bar(
|
||||
ui: &mut Ui,
|
||||
title_label: Label,
|
||||
title: WidgetText,
|
||||
show_close_button: bool,
|
||||
collapsing_id: Id,
|
||||
collapsing: &mut collapsing_header::State,
|
||||
collapsible: bool,
|
||||
) -> TitleBar {
|
||||
let inner_response = ui.horizontal(|ui| {
|
||||
let height = title_label
|
||||
let height = title
|
||||
.font_height(ui.fonts(), ui.style())
|
||||
.max(ui.spacing().interact_size.y);
|
||||
ui.set_min_height(height);
|
||||
|
@ -782,7 +779,7 @@ fn show_title_bar(
|
|||
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 {
|
||||
// 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 {
|
||||
id,
|
||||
title_label,
|
||||
title_galley,
|
||||
min_rect,
|
||||
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 text_pos =
|
||||
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_color = ui.visuals().text_color();
|
||||
self.title_label
|
||||
.paint_galley(ui, text_pos, self.title_galley, false, text_color);
|
||||
self.title_galley.paint_with_fallback_color(
|
||||
ui.painter(),
|
||||
text_pos,
|
||||
ui.visuals().text_color(),
|
||||
);
|
||||
|
||||
if let Some(content_response) = &content_response {
|
||||
// paint separator between title and content:
|
||||
|
|
|
@ -915,7 +915,7 @@ impl Context {
|
|||
let text = format!("{} - {:?}", layer_id.short_debug_format(), area.rect(),);
|
||||
// TODO: `Sense::hover_highlight()`
|
||||
if ui
|
||||
.add(Label::new(text).monospace().sense(Sense::click()))
|
||||
.add(Label::new(RichText::new(text).monospace()).sense(Sense::click()))
|
||||
.hovered
|
||||
&& is_visible
|
||||
{
|
||||
|
|
|
@ -367,6 +367,7 @@ mod sense;
|
|||
pub mod style;
|
||||
mod ui;
|
||||
pub mod util;
|
||||
mod widget_text;
|
||||
pub mod widgets;
|
||||
|
||||
pub use epaint;
|
||||
|
@ -408,6 +409,7 @@ pub use {
|
|||
style::{Style, Visuals},
|
||||
text::{Galley, TextFormat},
|
||||
ui::Ui,
|
||||
widget_text::{RichText, WidgetText},
|
||||
widgets::*,
|
||||
};
|
||||
|
||||
|
@ -416,10 +418,10 @@ pub use {
|
|||
/// Helper function that adds a label when compiling with debug assertions enabled.
|
||||
pub fn warn_if_debug_build(ui: &mut crate::Ui) {
|
||||
if cfg!(debug_assertions) {
|
||||
ui.add(
|
||||
crate::Label::new("‼ Debug build ‼")
|
||||
ui.label(
|
||||
RichText::new("‼ Debug build ‼")
|
||||
.small()
|
||||
.text_color(crate::Color32::RED),
|
||||
.color(crate::Color32::RED),
|
||||
)
|
||||
.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 {
|
||||
($github_url: expr, $label: expr) => {{
|
||||
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 {
|
||||
($github_url: expr, $label: expr) => {{
|
||||
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.
|
||||
pub fn menu_button<R>(
|
||||
ui: &mut Ui,
|
||||
title: impl ToString,
|
||||
title: impl Into<WidgetText>,
|
||||
add_contents: impl FnOnce(&mut Ui) -> R,
|
||||
) -> InnerResponse<Option<R>> {
|
||||
stationary_menu_impl(ui, title, Box::new(add_contents))
|
||||
|
@ -103,18 +103,17 @@ pub fn menu_button<R>(
|
|||
pub(crate) fn submenu_button<R>(
|
||||
ui: &mut Ui,
|
||||
parent_state: Arc<RwLock<MenuState>>,
|
||||
title: impl ToString,
|
||||
title: impl Into<WidgetText>,
|
||||
add_contents: impl FnOnce(&mut Ui) -> R,
|
||||
) -> InnerResponse<Option<R>> {
|
||||
SubMenu::new(parent_state, title).show(ui, add_contents)
|
||||
}
|
||||
|
||||
/// wrapper for the contents of every menu.
|
||||
#[allow(clippy::needless_pass_by_value)]
|
||||
pub(crate) fn menu_ui<'c, R>(
|
||||
ctx: &CtxRef,
|
||||
menu_id: impl std::hash::Hash,
|
||||
menu_state_arc: Arc<RwLock<MenuState>>,
|
||||
menu_state_arc: &Arc<RwLock<MenuState>>,
|
||||
mut style: Style,
|
||||
add_contents: impl FnOnce(&mut Ui) -> R + 'c,
|
||||
) -> InnerResponse<R> {
|
||||
|
@ -152,15 +151,14 @@ pub(crate) fn menu_ui<'c, R>(
|
|||
}
|
||||
|
||||
/// build a top level menu with a button
|
||||
#[allow(clippy::needless_pass_by_value)]
|
||||
fn stationary_menu_impl<'c, R>(
|
||||
ui: &mut Ui,
|
||||
title: impl ToString,
|
||||
title: impl Into<WidgetText>,
|
||||
add_contents: Box<dyn FnOnce(&mut Ui) -> R + 'c>,
|
||||
) -> InnerResponse<Option<R>> {
|
||||
let title = title.to_string();
|
||||
let title = title.into();
|
||||
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);
|
||||
|
||||
|
@ -372,20 +370,20 @@ impl MenuResponse {
|
|||
}
|
||||
}
|
||||
pub struct SubMenuButton {
|
||||
text: String,
|
||||
icon: String,
|
||||
text: WidgetText,
|
||||
icon: WidgetText,
|
||||
index: usize,
|
||||
}
|
||||
impl SubMenuButton {
|
||||
/// 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 ToString, icon: impl ToString, index: usize) -> Self {
|
||||
fn new(text: impl Into<WidgetText>, icon: impl Into<WidgetText>, index: usize) -> Self {
|
||||
Self {
|
||||
text: text.to_string(),
|
||||
icon: icon.to_string(),
|
||||
text: text.into(),
|
||||
icon: icon.into(),
|
||||
index,
|
||||
}
|
||||
}
|
||||
|
||||
fn visuals<'a>(
|
||||
ui: &'a Ui,
|
||||
response: &'_ Response,
|
||||
|
@ -398,11 +396,12 @@ impl SubMenuButton {
|
|||
ui.style().interact(response)
|
||||
}
|
||||
}
|
||||
#[allow(clippy::needless_pass_by_value)]
|
||||
pub fn icon(mut self, icon: impl ToString) -> Self {
|
||||
self.icon = icon.to_string();
|
||||
|
||||
pub fn icon(mut self, icon: impl Into<WidgetText>) -> Self {
|
||||
self.icon = icon.into();
|
||||
self
|
||||
}
|
||||
|
||||
pub(crate) fn show(self, ui: &mut Ui, menu_state: &MenuState, sub_id: Id) -> Response {
|
||||
let SubMenuButton { text, icon, .. } = self;
|
||||
|
||||
|
@ -412,14 +411,10 @@ impl SubMenuButton {
|
|||
let button_padding = ui.spacing().button_padding;
|
||||
let total_extra = button_padding + button_padding;
|
||||
let text_available_width = ui.available_width() - total_extra.x;
|
||||
let text_galley = ui
|
||||
.fonts()
|
||||
.layout_delayed_color(text, text_style, text_available_width);
|
||||
let text_galley = text.into_galley(ui, Some(true), text_available_width, text_style);
|
||||
|
||||
let icon_available_width = text_available_width - text_galley.size().x;
|
||||
let icon_galley = ui
|
||||
.fonts()
|
||||
.layout_delayed_color(icon, text_style, icon_available_width);
|
||||
let icon_galley = icon.into_galley(ui, Some(true), icon_available_width, text_style);
|
||||
let text_and_icon_size = Vec2::new(
|
||||
text_galley.size().x + icon_galley.size().x,
|
||||
text_galley.size().y.max(icon_galley.size().y),
|
||||
|
@ -447,10 +442,8 @@ impl SubMenuButton {
|
|||
);
|
||||
|
||||
let text_color = visuals.text_color();
|
||||
ui.painter()
|
||||
.galley_with_color(text_pos, text_galley, text_color);
|
||||
ui.painter()
|
||||
.galley_with_color(icon_pos, icon_galley, text_color);
|
||||
text_galley.paint_with_fallback_color(ui.painter(), text_pos, text_color);
|
||||
icon_galley.paint_with_fallback_color(ui.painter(), icon_pos, text_color);
|
||||
}
|
||||
response
|
||||
}
|
||||
|
@ -460,14 +453,14 @@ pub struct SubMenu {
|
|||
parent_state: Arc<RwLock<MenuState>>,
|
||||
}
|
||||
impl SubMenu {
|
||||
#[allow(clippy::needless_pass_by_value)]
|
||||
fn new(parent_state: Arc<RwLock<MenuState>>, text: impl ToString) -> Self {
|
||||
fn new(parent_state: Arc<RwLock<MenuState>>, text: impl Into<WidgetText>) -> Self {
|
||||
let index = parent_state.write().next_entry_index();
|
||||
Self {
|
||||
button: SubMenuButton::new(text, "⏵", index),
|
||||
parent_state,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn show<R>(
|
||||
self,
|
||||
ui: &mut Ui,
|
||||
|
@ -522,8 +515,7 @@ impl MenuState {
|
|||
},
|
||||
..Default::default()
|
||||
};
|
||||
let menu_state_arc = menu_state.clone();
|
||||
crate::menu::menu_ui(ctx, id, menu_state_arc, style, add_contents)
|
||||
crate::menu::menu_ui(ctx, id, menu_state, style, add_contents)
|
||||
}
|
||||
fn show_submenu<R>(
|
||||
&mut self,
|
||||
|
|
|
@ -1,8 +1,7 @@
|
|||
use crate::{
|
||||
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.
|
||||
#[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| {
|
||||
ui.add(crate::widgets::Label::new(text));
|
||||
})
|
||||
}
|
||||
|
||||
/// 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| {
|
||||
ui.add(crate::widgets::Label::new(text));
|
||||
})
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
#![allow(clippy::if_same_then_else)]
|
||||
|
||||
use crate::{color::*, emath::*, Response};
|
||||
use crate::{color::*, emath::*, Response, RichText, WidgetText};
|
||||
use epaint::{Shadow, Stroke, TextStyle};
|
||||
|
||||
/// Specifies the look and feel of egui.
|
||||
|
@ -581,15 +581,8 @@ impl Style {
|
|||
ui.label("Default body text style:");
|
||||
ui.horizontal(|ui| {
|
||||
for &style in &[TextStyle::Body, TextStyle::Monospace] {
|
||||
if ui
|
||||
.add(
|
||||
RadioButton::new(*body_text_style == style, format!("{:?}", style))
|
||||
.text_style(style),
|
||||
)
|
||||
.clicked()
|
||||
{
|
||||
*body_text_style = style;
|
||||
};
|
||||
let text = crate::RichText::new(format!("{:?}", style)).text_style(style);
|
||||
ui.radio_value(body_text_style, style, text);
|
||||
}
|
||||
});
|
||||
ui.end_row();
|
||||
|
@ -603,17 +596,8 @@ impl Style {
|
|||
.show_ui(ui, |ui| {
|
||||
ui.selectable_value(override_text_style, None, "None");
|
||||
for style in TextStyle::all() {
|
||||
// ui.selectable_value(override_text_style, Some(style), format!("{:?}", style));
|
||||
let selected = *override_text_style == Some(style);
|
||||
if ui
|
||||
.add(
|
||||
SelectableLabel::new(selected, format!("{:?}", style))
|
||||
.text_style(style),
|
||||
)
|
||||
.clicked()
|
||||
{
|
||||
*override_text_style = Some(style);
|
||||
}
|
||||
let text = crate::RichText::new(format!("{:?}", style)).text_style(style);
|
||||
ui.selectable_value(override_text_style, Some(style), text);
|
||||
}
|
||||
});
|
||||
ui.end_row();
|
||||
|
@ -879,7 +863,7 @@ impl Visuals {
|
|||
&mut widgets.noninteractive.fg_stroke.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.spacing_mut().item_spacing.x = 0.0;
|
||||
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.color_edit_button_srgba(srgba);
|
||||
ui.add(label.into());
|
||||
ui.label(label);
|
||||
})
|
||||
.response
|
||||
}
|
||||
|
|
|
@ -283,6 +283,7 @@ impl Ui {
|
|||
}
|
||||
|
||||
/// Should text wrap in this `Ui`?
|
||||
///
|
||||
/// This is determined first by [`Style::wrap`], and then by the layout of this `Ui`.
|
||||
pub fn wrap_text(&self) -> bool {
|
||||
if let Some(wrap) = self.style.wrap {
|
||||
|
@ -290,8 +291,8 @@ impl Ui {
|
|||
} else if let Some(grid) = self.placer.grid() {
|
||||
grid.wrap_text()
|
||||
} else {
|
||||
// In vertical layouts we wrap text, but in horizontal we keep going.
|
||||
self.layout().is_vertical()
|
||||
let layout = self.layout();
|
||||
layout.is_vertical() || layout.is_horizontal() && layout.main_wrap()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -990,50 +991,54 @@ impl Ui {
|
|||
///
|
||||
/// See also [`Label`].
|
||||
#[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)
|
||||
}
|
||||
|
||||
/// Show colored text.
|
||||
///
|
||||
/// Shortcut for `add(Label::new(text).text_color(color))`
|
||||
pub fn colored_label(&mut self, color: impl Into<Color32>, text: impl ToString) -> Response {
|
||||
Label::new(text).text_color(color).ui(self)
|
||||
/// Shortcut for `ui.label(RichText::new(text).color(color))`
|
||||
pub fn colored_label(
|
||||
&mut self,
|
||||
color: impl Into<Color32>,
|
||||
text: impl Into<RichText>,
|
||||
) -> Response {
|
||||
Label::new(text.into().color(color)).ui(self)
|
||||
}
|
||||
|
||||
/// Show large text.
|
||||
///
|
||||
/// Shortcut for `add(Label::new(text).heading())`
|
||||
pub fn heading(&mut self, text: impl ToString) -> Response {
|
||||
Label::new(text).heading().ui(self)
|
||||
/// Shortcut for `ui.label(RichText::new(text).heading())`
|
||||
pub fn heading(&mut self, text: impl Into<RichText>) -> Response {
|
||||
Label::new(text.into().heading()).ui(self)
|
||||
}
|
||||
|
||||
/// Show monospace (fixed width) text.
|
||||
///
|
||||
/// Shortcut for `add(Label::new(text).monospace())`
|
||||
pub fn monospace(&mut self, text: impl ToString) -> Response {
|
||||
Label::new(text).monospace().ui(self)
|
||||
/// Shortcut for `ui.label(RichText::new(text).monospace())`
|
||||
pub fn monospace(&mut self, text: impl Into<RichText>) -> Response {
|
||||
Label::new(text.into().monospace()).ui(self)
|
||||
}
|
||||
|
||||
/// Show text as monospace with a gray background.
|
||||
///
|
||||
/// Shortcut for `add(Label::new(text).code())`
|
||||
pub fn code(&mut self, text: impl ToString) -> Response {
|
||||
Label::new(text).code().ui(self)
|
||||
/// Shortcut for `ui.label(RichText::new(text).code())`
|
||||
pub fn code(&mut self, text: impl Into<RichText>) -> Response {
|
||||
Label::new(text.into().code()).ui(self)
|
||||
}
|
||||
|
||||
/// Show small text.
|
||||
///
|
||||
/// Shortcut for `add(Label::new(text).small())`
|
||||
pub fn small(&mut self, text: impl ToString) -> Response {
|
||||
Label::new(text).small().ui(self)
|
||||
/// Shortcut for `ui.label(RichText::new(text).small())`
|
||||
pub fn small(&mut self, text: impl Into<RichText>) -> Response {
|
||||
Label::new(text.into().small()).ui(self)
|
||||
}
|
||||
|
||||
/// Show text that stand out a bit (e.g. slightly brighter).
|
||||
///
|
||||
/// Shortcut for `add(Label::new(text).strong())`
|
||||
pub fn strong(&mut self, text: impl ToString) -> Response {
|
||||
Label::new(text).strong().ui(self)
|
||||
/// Shortcut for `ui.label(RichText::new(text).strong())`
|
||||
pub fn strong(&mut self, text: impl Into<RichText>) -> Response {
|
||||
Label::new(text.into().strong()).ui(self)
|
||||
}
|
||||
|
||||
/// Shortcut for `add(Hyperlink::new(url))`
|
||||
|
@ -1051,8 +1056,8 @@ impl Ui {
|
|||
/// ```
|
||||
///
|
||||
/// See also [`Hyperlink`].
|
||||
pub fn hyperlink_to(&mut self, label: impl ToString, url: impl ToString) -> Response {
|
||||
Hyperlink::new(url).text(label).ui(self)
|
||||
pub fn hyperlink_to(&mut self, label: impl Into<WidgetText>, url: impl ToString) -> Response {
|
||||
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`).
|
||||
|
@ -1089,9 +1094,21 @@ impl Ui {
|
|||
/// Shortcut for `add(Button::new(text))`
|
||||
///
|
||||
/// 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() { … } "]
|
||||
#[inline(always)]
|
||||
pub fn button(&mut self, text: impl ToString) -> Response {
|
||||
#[inline]
|
||||
pub fn button(&mut self, text: impl Into<WidgetText>) -> Response {
|
||||
Button::new(text).ui(self)
|
||||
}
|
||||
|
||||
|
@ -1101,19 +1118,21 @@ impl Ui {
|
|||
///
|
||||
/// Shortcut for `add(Button::new(text).small())`
|
||||
#[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)
|
||||
}
|
||||
|
||||
/// 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)
|
||||
}
|
||||
|
||||
/// Show a [`RadioButton`].
|
||||
/// Often you want to use [`Self::radio_value`] instead.
|
||||
#[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)
|
||||
}
|
||||
|
||||
|
@ -1134,11 +1153,12 @@ impl Ui {
|
|||
/// if ui.add(egui::RadioButton::new(my_enum == Enum::First, "First")).clicked() {
|
||||
/// my_enum = Enum::First
|
||||
/// }
|
||||
/// ```
|
||||
pub fn radio_value<Value: PartialEq>(
|
||||
&mut self,
|
||||
current_value: &mut Value,
|
||||
selected_value: Value,
|
||||
text: impl ToString,
|
||||
text: impl Into<WidgetText>,
|
||||
) -> Response {
|
||||
let mut response = self.radio(*current_value == selected_value, text);
|
||||
if response.clicked() {
|
||||
|
@ -1152,7 +1172,7 @@ impl Ui {
|
|||
///
|
||||
/// See also [`SelectableLabel`].
|
||||
#[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)
|
||||
}
|
||||
|
||||
|
@ -1166,7 +1186,7 @@ impl Ui {
|
|||
&mut self,
|
||||
current_value: &mut Value,
|
||||
selected_value: Value,
|
||||
text: impl ToString,
|
||||
text: impl Into<WidgetText>,
|
||||
) -> Response {
|
||||
let mut response = self.selectable_label(*current_value == selected_value, text);
|
||||
if response.clicked() {
|
||||
|
@ -1373,7 +1393,7 @@ impl Ui {
|
|||
/// A [`CollapsingHeader`] that starts out collapsed.
|
||||
pub fn collapsing<R>(
|
||||
&mut self,
|
||||
heading: impl ToString,
|
||||
heading: impl Into<WidgetText>,
|
||||
add_contents: impl FnOnce(&mut Ui) -> R,
|
||||
) -> CollapsingResponse<R> {
|
||||
CollapsingHeader::new(heading).show(self, add_contents)
|
||||
|
@ -1642,10 +1662,6 @@ impl Ui {
|
|||
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.
|
||||
/// Otherwise does nothing.
|
||||
pub fn end_row(&mut self) {
|
||||
|
@ -1749,7 +1765,7 @@ impl Ui {
|
|||
/// ```
|
||||
pub fn menu_button<R>(
|
||||
&mut self,
|
||||
title: impl ToString,
|
||||
title: impl Into<WidgetText>,
|
||||
add_contents: impl FnOnce(&mut Ui) -> R,
|
||||
) -> InnerResponse<Option<R>> {
|
||||
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::*;
|
||||
|
||||
/// 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.
|
||||
///
|
||||
/// 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();
|
||||
/// # fn do_stuff() {}
|
||||
///
|
||||
/// if ui.add(egui::Button::new("Click mew")).clicked() {
|
||||
/// if ui.add(egui::Button::new("Click me")).clicked() {
|
||||
/// 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);`"]
|
||||
pub struct Button {
|
||||
text: String,
|
||||
text_color: Option<Color32>,
|
||||
text_style: Option<TextStyle>,
|
||||
text: WidgetText,
|
||||
wrap: Option<bool>,
|
||||
/// None means default for interact
|
||||
fill: Option<Color32>,
|
||||
stroke: Option<Stroke>,
|
||||
sense: Sense,
|
||||
small: bool,
|
||||
frame: Option<bool>,
|
||||
wrap: Option<bool>,
|
||||
min_size: Vec2,
|
||||
}
|
||||
|
||||
impl Button {
|
||||
#[allow(clippy::needless_pass_by_value)]
|
||||
pub fn new(text: impl ToString) -> Self {
|
||||
pub fn new(text: impl Into<WidgetText>) -> Self {
|
||||
Self {
|
||||
text: text.to_string(),
|
||||
text_color: None,
|
||||
text_style: None,
|
||||
text: text.into(),
|
||||
wrap: None,
|
||||
fill: None,
|
||||
stroke: None,
|
||||
sense: Sense::click(),
|
||||
small: false,
|
||||
frame: None,
|
||||
wrap: None,
|
||||
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 {
|
||||
self.text_color = Some(text_color);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn text_color_opt(mut self, text_color: Option<Color32>) -> Self {
|
||||
self.text_color = text_color;
|
||||
self.text = self.text.color(text_color);
|
||||
self
|
||||
}
|
||||
|
||||
#[deprecated = "Replaced by: Button::new(RichText::new(text).text_style(…))"]
|
||||
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
|
||||
}
|
||||
|
||||
|
@ -91,7 +87,7 @@ impl Button {
|
|||
|
||||
/// Make this a small button, suitable for embedding into text.
|
||||
pub fn small(mut self) -> Self {
|
||||
self.text_style = Some(TextStyle::Body);
|
||||
self.text = self.text.text_style(TextStyle::Body);
|
||||
self.small = true;
|
||||
self
|
||||
}
|
||||
|
@ -109,17 +105,6 @@ impl Button {
|
|||
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 {
|
||||
self.min_size = min_size;
|
||||
self
|
||||
|
@ -130,49 +115,40 @@ impl Widget for Button {
|
|||
fn ui(self, ui: &mut Ui) -> Response {
|
||||
let Button {
|
||||
text,
|
||||
text_color,
|
||||
text_style,
|
||||
wrap,
|
||||
fill,
|
||||
stroke,
|
||||
sense,
|
||||
small,
|
||||
frame,
|
||||
wrap,
|
||||
min_size,
|
||||
} = self;
|
||||
|
||||
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;
|
||||
if small {
|
||||
button_padding.y = 0.0;
|
||||
}
|
||||
let total_extra = button_padding + button_padding;
|
||||
|
||||
let wrap = wrap.unwrap_or_else(|| ui.wrap_text());
|
||||
let wrap_width = select(wrap, ui.available_width() - total_extra.x, f32::INFINITY);
|
||||
let galley = ui
|
||||
.fonts()
|
||||
.layout_delayed_color(text, text_style, wrap_width);
|
||||
let wrap_width = ui.available_width() - total_extra.x;
|
||||
let text = text.into_galley(ui, wrap, wrap_width, TextStyle::Button);
|
||||
|
||||
let mut desired_size = galley.size() + 2.0 * button_padding;
|
||||
let mut desired_size = text.size() + 2.0 * button_padding;
|
||||
if !small {
|
||||
desired_size.y = desired_size.y.at_least(ui.spacing().interact_size.y);
|
||||
}
|
||||
desired_size = desired_size.at_least(min_size);
|
||||
|
||||
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) {
|
||||
let visuals = ui.style().interact(&response);
|
||||
let text_pos = ui
|
||||
.layout()
|
||||
.align_size_within_rect(galley.size(), rect.shrink2(button_padding))
|
||||
.align_size_within_rect(text.size(), rect.shrink2(button_padding))
|
||||
.min;
|
||||
|
||||
if frame {
|
||||
|
@ -186,10 +162,7 @@ impl Widget for Button {
|
|||
);
|
||||
}
|
||||
|
||||
let text_color = text_color
|
||||
.or(ui.visuals().override_text_color)
|
||||
.unwrap_or_else(|| visuals.text_color());
|
||||
ui.painter().galley_with_color(text_pos, galley, text_color);
|
||||
text.paint_with_visuals(ui.painter(), text_pos, visuals);
|
||||
}
|
||||
|
||||
response
|
||||
|
@ -211,48 +184,35 @@ impl Widget for Button {
|
|||
/// ui.add(egui::Checkbox::new(&mut my_bool, "Checked"));
|
||||
/// ```
|
||||
#[must_use = "You should put this widget in an ui with `ui.add(widget);`"]
|
||||
#[derive(Debug)]
|
||||
pub struct Checkbox<'a> {
|
||||
checked: &'a mut bool,
|
||||
text: String,
|
||||
text_color: Option<Color32>,
|
||||
text_style: Option<TextStyle>,
|
||||
text: WidgetText,
|
||||
}
|
||||
|
||||
impl<'a> Checkbox<'a> {
|
||||
#[allow(clippy::needless_pass_by_value)]
|
||||
pub fn new(checked: &'a mut bool, text: impl ToString) -> Self {
|
||||
pub fn new(checked: &'a mut bool, text: impl Into<WidgetText>) -> Self {
|
||||
Checkbox {
|
||||
checked,
|
||||
text: text.to_string(),
|
||||
text_color: None,
|
||||
text_style: None,
|
||||
text: text.into(),
|
||||
}
|
||||
}
|
||||
|
||||
#[deprecated = "Replaced by: Checkbox::new(RichText::new(text).color(…))"]
|
||||
pub fn text_color(mut self, text_color: Color32) -> Self {
|
||||
self.text_color = Some(text_color);
|
||||
self.text = self.text.color(text_color);
|
||||
self
|
||||
}
|
||||
|
||||
#[deprecated = "Replaced by: Checkbox::new(RichText::new(text).text_style(…))"]
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Widget for Checkbox<'a> {
|
||||
fn ui(self, ui: &mut Ui) -> Response {
|
||||
let Checkbox {
|
||||
checked,
|
||||
text,
|
||||
text_color,
|
||||
text_style,
|
||||
} = self;
|
||||
|
||||
let text_style = text_style
|
||||
.or(ui.style().override_text_style)
|
||||
.unwrap_or(TextStyle::Button);
|
||||
let Checkbox { checked, text } = self;
|
||||
|
||||
let spacing = &ui.spacing();
|
||||
let icon_width = spacing.icon_width;
|
||||
|
@ -260,16 +220,10 @@ impl<'a> Widget for Checkbox<'a> {
|
|||
let button_padding = spacing.button_padding;
|
||||
let total_extra = button_padding + vec2(icon_width + icon_spacing, 0.0) + button_padding;
|
||||
|
||||
let wrap_width = select(
|
||||
ui.wrap_text(),
|
||||
ui.available_width() - total_extra.x,
|
||||
f32::INFINITY,
|
||||
);
|
||||
let galley = ui
|
||||
.fonts()
|
||||
.layout_delayed_color(text, text_style, wrap_width);
|
||||
let wrap_width = ui.available_width() - total_extra.x;
|
||||
let text = text.into_galley(ui, None, wrap_width, TextStyle::Button);
|
||||
|
||||
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.y = desired_size.y.max(icon_width);
|
||||
let (rect, mut response) = ui.allocate_exact_size(desired_size, Sense::click());
|
||||
|
@ -278,14 +232,13 @@ impl<'a> Widget for Checkbox<'a> {
|
|||
*checked = !*checked;
|
||||
response.mark_changed();
|
||||
}
|
||||
response
|
||||
.widget_info(|| WidgetInfo::selected(WidgetType::Checkbox, *checked, galley.text()));
|
||||
response.widget_info(|| WidgetInfo::selected(WidgetType::Checkbox, *checked, text.text()));
|
||||
|
||||
// let visuals = ui.style().interact_selectable(&response, *checked); // too colorful
|
||||
let visuals = ui.style().interact(&response);
|
||||
let text_pos = pos2(
|
||||
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);
|
||||
ui.painter().add(epaint::RectShape {
|
||||
|
@ -307,10 +260,7 @@ impl<'a> Widget for Checkbox<'a> {
|
|||
));
|
||||
}
|
||||
|
||||
let text_color = text_color
|
||||
.or(ui.visuals().override_text_color)
|
||||
.unwrap_or_else(|| visuals.text_color());
|
||||
ui.painter().galley_with_color(text_pos, galley, text_color);
|
||||
text.paint_with_visuals(ui.painter(), text_pos, visuals);
|
||||
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);`"]
|
||||
#[derive(Debug)]
|
||||
pub struct RadioButton {
|
||||
checked: bool,
|
||||
text: String,
|
||||
text_color: Option<Color32>,
|
||||
text_style: Option<TextStyle>,
|
||||
text: WidgetText,
|
||||
}
|
||||
|
||||
impl RadioButton {
|
||||
#[allow(clippy::needless_pass_by_value)]
|
||||
pub fn new(checked: bool, text: impl ToString) -> Self {
|
||||
pub fn new(checked: bool, text: impl Into<WidgetText>) -> Self {
|
||||
Self {
|
||||
checked,
|
||||
text: text.to_string(),
|
||||
text_color: None,
|
||||
text_style: None,
|
||||
text: text.into(),
|
||||
}
|
||||
}
|
||||
|
||||
#[deprecated = "Replaced by: RadioButton::new(RichText::new(text).color(…))"]
|
||||
pub fn text_color(mut self, text_color: Color32) -> Self {
|
||||
self.text_color = Some(text_color);
|
||||
self.text = self.text.color(text_color);
|
||||
self
|
||||
}
|
||||
|
||||
#[deprecated = "Replaced by: RadioButton::new(RichText::new(text).text_style(…))"]
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
impl Widget for RadioButton {
|
||||
fn ui(self, ui: &mut Ui) -> Response {
|
||||
let RadioButton {
|
||||
checked,
|
||||
text,
|
||||
text_color,
|
||||
text_style,
|
||||
} = self;
|
||||
|
||||
let text_style = text_style
|
||||
.or(ui.style().override_text_style)
|
||||
.unwrap_or(TextStyle::Button);
|
||||
let RadioButton { checked, text } = self;
|
||||
|
||||
let icon_width = ui.spacing().icon_width;
|
||||
let icon_spacing = ui.spacing().icon_spacing;
|
||||
let button_padding = ui.spacing().button_padding;
|
||||
let total_extra = button_padding + vec2(icon_width + icon_spacing, 0.0) + button_padding;
|
||||
|
||||
let wrap_width = select(
|
||||
ui.wrap_text(),
|
||||
ui.available_width() - total_extra.x,
|
||||
f32::INFINITY,
|
||||
);
|
||||
let galley = ui
|
||||
.fonts()
|
||||
.layout_delayed_color(text, text_style, wrap_width);
|
||||
let wrap_width = ui.available_width() - total_extra.x;
|
||||
let text = text.into_galley(ui, None, wrap_width, TextStyle::Button);
|
||||
|
||||
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.y = desired_size.y.max(icon_width);
|
||||
let (rect, response) = ui.allocate_exact_size(desired_size, Sense::click());
|
||||
response
|
||||
.widget_info(|| WidgetInfo::selected(WidgetType::RadioButton, checked, galley.text()));
|
||||
.widget_info(|| WidgetInfo::selected(WidgetType::RadioButton, checked, text.text()));
|
||||
|
||||
let text_pos = pos2(
|
||||
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
|
||||
|
@ -429,10 +360,7 @@ impl Widget for RadioButton {
|
|||
});
|
||||
}
|
||||
|
||||
let text_color = text_color
|
||||
.or(ui.visuals().override_text_color)
|
||||
.unwrap_or_else(|| visuals.text_color());
|
||||
painter.galley_with_color(text_pos, galley, text_color);
|
||||
text.paint_with_visuals(ui.painter(), text_pos, visuals);
|
||||
response
|
||||
}
|
||||
}
|
||||
|
|
|
@ -208,11 +208,12 @@ impl<'a> Widget for DragValue<'a> {
|
|||
}
|
||||
response
|
||||
} else {
|
||||
let button = Button::new(format!("{}{}{}", prefix, value_text, suffix))
|
||||
.sense(Sense::click_and_drag())
|
||||
.text_style(TextStyle::Monospace)
|
||||
let button = Button::new(
|
||||
RichText::new(format!("{}{}{}", prefix, value_text, suffix)).monospace(),
|
||||
)
|
||||
.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 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);`"]
|
||||
pub struct Hyperlink {
|
||||
url: String,
|
||||
label: Label,
|
||||
text: WidgetText,
|
||||
}
|
||||
|
||||
impl Hyperlink {
|
||||
|
@ -21,41 +21,45 @@ impl Hyperlink {
|
|||
let url = url.to_string();
|
||||
Self {
|
||||
url: url.clone(),
|
||||
label: Label::new(url).sense(Sense::click()),
|
||||
text: url.into(),
|
||||
}
|
||||
}
|
||||
|
||||
#[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 {
|
||||
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)]
|
||||
pub fn text(mut self, text: impl ToString) -> Self {
|
||||
self.label.text = text.to_string();
|
||||
self.text = text.to_string().into();
|
||||
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 {
|
||||
self.label = self.label.text_style(text_style);
|
||||
self.text = self.text.text_style(text_style);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn small(self) -> Self {
|
||||
self.text_style(TextStyle::Small)
|
||||
#[deprecated = "Use Hyperlink::from_label_and_url instead"]
|
||||
pub fn small(mut self) -> Self {
|
||||
self.text = self.text.text_style(TextStyle::Small);
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl Widget for Hyperlink {
|
||||
fn ui(self, ui: &mut Ui) -> Response {
|
||||
let Hyperlink { url, label } = self;
|
||||
let (pos, galley, response) = label.layout_in_ui(ui);
|
||||
response.widget_info(|| WidgetInfo::labeled(WidgetType::Hyperlink, galley.text()));
|
||||
let Hyperlink { url, text } = self;
|
||||
let label = Label::new(text).sense(Sense::click());
|
||||
|
||||
let (pos, text_galley, response) = label.layout_in_ui(ui);
|
||||
response.widget_info(|| WidgetInfo::labeled(WidgetType::Hyperlink, text_galley.text()));
|
||||
|
||||
if response.hovered() {
|
||||
ui.ctx().output().cursor_icon = CursorIcon::PointingHand;
|
||||
|
@ -85,7 +89,7 @@ impl Widget for Hyperlink {
|
|||
|
||||
ui.painter().add(epaint::TextShape {
|
||||
pos,
|
||||
galley,
|
||||
galley: text_galley.galley,
|
||||
override_text_color: Some(color),
|
||||
underline,
|
||||
angle: 0.0,
|
||||
|
|
|
@ -1,9 +1,4 @@
|
|||
use crate::*;
|
||||
use epaint::{
|
||||
text::{LayoutJob, LayoutSection, TextFormat},
|
||||
Galley,
|
||||
};
|
||||
use std::sync::Arc;
|
||||
use crate::{widget_text::WidgetTextGalley, *};
|
||||
|
||||
/// Static text.
|
||||
///
|
||||
|
@ -11,135 +6,123 @@ use std::sync::Arc;
|
|||
/// # let ui = &mut egui::Ui::__test();
|
||||
/// ui.label("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);`"]
|
||||
pub struct Label {
|
||||
// TODO: not pub
|
||||
pub(crate) text: String,
|
||||
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,
|
||||
text: WidgetText,
|
||||
wrap: Option<bool>,
|
||||
sense: Sense,
|
||||
}
|
||||
|
||||
impl Label {
|
||||
#[allow(clippy::needless_pass_by_value)]
|
||||
pub fn new(text: impl ToString) -> Self {
|
||||
pub fn new(text: impl Into<WidgetText>) -> Self {
|
||||
Self {
|
||||
text: text.to_string(),
|
||||
text: text.into(),
|
||||
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(),
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
/// and horizontal layouts with wrapping,
|
||||
/// 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 {
|
||||
self.wrap = Some(wrap);
|
||||
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 {
|
||||
self.text_style = Some(text_style);
|
||||
self.text = self.text.text_style(text_style);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn heading(self) -> Self {
|
||||
self.text_style(TextStyle::Heading)
|
||||
#[deprecated = "Replaced by Label::new(RichText::new(…).heading())"]
|
||||
pub fn heading(mut self) -> Self {
|
||||
self.text = self.text.heading();
|
||||
self
|
||||
}
|
||||
|
||||
pub fn monospace(self) -> Self {
|
||||
self.text_style(TextStyle::Monospace)
|
||||
#[deprecated = "Replaced by Label::new(RichText::new(…).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 {
|
||||
self.code = true;
|
||||
self.text_style(TextStyle::Monospace)
|
||||
self.text = self.text.code();
|
||||
self
|
||||
}
|
||||
|
||||
/// Extra strong text (stronger color).
|
||||
#[deprecated = "Replaced by Label::new(RichText::new(…).strong())"]
|
||||
pub fn strong(mut self) -> Self {
|
||||
self.strong = true;
|
||||
self.text = self.text.strong();
|
||||
self
|
||||
}
|
||||
|
||||
/// Extra weak text (fainter color).
|
||||
#[deprecated = "Replaced by Label::new(RichText::new(…).weak())"]
|
||||
pub fn weak(mut self) -> Self {
|
||||
self.weak = true;
|
||||
self.text = self.text.weak();
|
||||
self
|
||||
}
|
||||
|
||||
/// draw a line under the text
|
||||
#[deprecated = "Replaced by Label::new(RichText::new(…).underline())"]
|
||||
pub fn underline(mut self) -> Self {
|
||||
self.underline = true;
|
||||
self.text = self.text.underline();
|
||||
self
|
||||
}
|
||||
|
||||
/// draw a line through the text, crossing it out
|
||||
#[deprecated = "Replaced by Label::new(RichText::new(…).strikethrough())"]
|
||||
pub fn strikethrough(mut self) -> Self {
|
||||
self.strikethrough = true;
|
||||
self.text = self.text.strikethrough();
|
||||
self
|
||||
}
|
||||
|
||||
/// tilt the characters to the right.
|
||||
#[deprecated = "Replaced by Label::new(RichText::new(…).italics())"]
|
||||
pub fn italics(mut self) -> Self {
|
||||
self.italics = true;
|
||||
self.text = self.text.italics();
|
||||
self
|
||||
}
|
||||
|
||||
/// Smaller text
|
||||
pub fn small(self) -> Self {
|
||||
self.text_style(TextStyle::Small)
|
||||
#[deprecated = "Replaced by Label::new(RichText::new(…).small())"]
|
||||
pub fn small(mut self) -> Self {
|
||||
self.text = self.text.small();
|
||||
self
|
||||
}
|
||||
|
||||
/// For e.g. exponents
|
||||
pub fn small_raised(self) -> Self {
|
||||
self.text_style(TextStyle::Small).raised()
|
||||
#[deprecated = "Replaced by Label::new(RichText::new(…).small_raised())"]
|
||||
pub fn small_raised(mut self) -> Self {
|
||||
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 {
|
||||
self.raised = true;
|
||||
self.text = self.text.raised();
|
||||
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 {
|
||||
self.background_color = background_color.into();
|
||||
self.text = self.text.background_color(background_color);
|
||||
self
|
||||
}
|
||||
|
||||
#[deprecated = "Replaced by Label::new(RichText::new(…).text_color())"]
|
||||
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
|
||||
}
|
||||
|
||||
|
@ -163,238 +146,119 @@ impl Label {
|
|||
}
|
||||
|
||||
impl Label {
|
||||
pub fn layout(&self, ui: &Ui) -> Arc<Galley> {
|
||||
let max_width = ui.available_width();
|
||||
let line_color = self.get_text_color(ui, ui.visuals().text_color());
|
||||
self.layout_width(ui, max_width, line_color)
|
||||
}
|
||||
|
||||
/// `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);
|
||||
/// Do layout and position the galley in the ui, without painting it or adding widget info.
|
||||
pub fn layout_in_ui(self, ui: &mut Ui) -> (Pos2, WidgetTextGalley, Response) {
|
||||
if let WidgetText::Galley(galley) = self.text {
|
||||
// If the user said "use this specific galley", then just use it:
|
||||
let (rect, response) = ui.allocate_exact_size(galley.size(), self.sense);
|
||||
let pos = match galley.job.halign {
|
||||
Align::LEFT => rect.left_top(),
|
||||
Align::Center => rect.center_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 {
|
||||
fn ui(self, ui: &mut Ui) -> Response {
|
||||
let (pos, galley, response) = self.layout_in_ui(ui);
|
||||
response.widget_info(|| WidgetInfo::labeled(WidgetType::Label, galley.text()));
|
||||
let (pos, text_galley, response) = self.layout_in_ui(ui);
|
||||
response.widget_info(|| WidgetInfo::labeled(WidgetType::Label, text_galley.text()));
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
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::*;
|
||||
|
||||
enum ProgressBarText {
|
||||
Custom(String),
|
||||
Custom(WidgetText),
|
||||
Percentage,
|
||||
}
|
||||
|
||||
|
@ -31,9 +31,8 @@ impl ProgressBar {
|
|||
}
|
||||
|
||||
/// A custom text to display on the progress bar.
|
||||
#[allow(clippy::needless_pass_by_value)]
|
||||
pub fn text(mut self, text: impl ToString) -> Self {
|
||||
self.text = Some(ProgressBarText::Custom(text.to_string()));
|
||||
pub fn text(mut self, text: impl Into<WidgetText>) -> Self {
|
||||
self.text = Some(ProgressBarText::Custom(text.into()));
|
||||
self
|
||||
}
|
||||
|
||||
|
@ -124,17 +123,19 @@ impl Widget for ProgressBar {
|
|||
|
||||
if let Some(text_kind) = text {
|
||||
let text = match text_kind {
|
||||
ProgressBarText::Custom(string) => string,
|
||||
ProgressBarText::Percentage => format!("{}%", (progress * 100.0) as usize),
|
||||
ProgressBarText::Custom(text) => text,
|
||||
ProgressBarText::Percentage => format!("{}%", (progress * 100.0) as usize).into(),
|
||||
};
|
||||
ui.painter().sub_region(outer_rect).text(
|
||||
outer_rect.left_center() + vec2(ui.spacing().item_spacing.x, 0.0),
|
||||
Align2::LEFT_CENTER,
|
||||
text,
|
||||
TextStyle::Button,
|
||||
visuals
|
||||
let galley = text.into_galley(ui, Some(false), f32::INFINITY, TextStyle::Button);
|
||||
let text_pos = outer_rect.left_center() - Vec2::new(0.0, galley.size().y / 2.0)
|
||||
+ vec2(ui.spacing().item_spacing.x, 0.0);
|
||||
let text_color = visuals
|
||||
.override_text_color
|
||||
.unwrap_or(visuals.selection.stroke.color),
|
||||
.unwrap_or(visuals.selection.stroke.color);
|
||||
galley.paint_with_fallback_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);`"]
|
||||
#[derive(Debug)]
|
||||
pub struct SelectableLabel {
|
||||
selected: bool,
|
||||
text: String,
|
||||
text_style: Option<TextStyle>,
|
||||
text: WidgetText,
|
||||
}
|
||||
|
||||
impl SelectableLabel {
|
||||
#[allow(clippy::needless_pass_by_value)]
|
||||
pub fn new(selected: bool, text: impl ToString) -> Self {
|
||||
pub fn new(selected: bool, text: impl Into<WidgetText>) -> Self {
|
||||
Self {
|
||||
selected,
|
||||
text: text.to_string(),
|
||||
text_style: None,
|
||||
text: text.into(),
|
||||
}
|
||||
}
|
||||
|
||||
#[deprecated = "Replaced by: Button::new(RichText::new(text).text_style(…))"]
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
impl Widget for SelectableLabel {
|
||||
fn ui(self, ui: &mut Ui) -> Response {
|
||||
let Self {
|
||||
selected,
|
||||
text,
|
||||
text_style,
|
||||
} = self;
|
||||
|
||||
let text_style = text_style
|
||||
.or(ui.style().override_text_style)
|
||||
.unwrap_or(TextStyle::Button);
|
||||
let Self { selected, text } = self;
|
||||
|
||||
let button_padding = ui.spacing().button_padding;
|
||||
let total_extra = button_padding + button_padding;
|
||||
|
||||
let wrap_width = if ui.wrap_text() {
|
||||
ui.available_width() - total_extra.x
|
||||
} else {
|
||||
f32::INFINITY
|
||||
};
|
||||
let wrap_width = ui.available_width() - total_extra.x;
|
||||
let text = text.into_galley(ui, None, wrap_width, TextStyle::Button);
|
||||
|
||||
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.y = desired_size.y.at_least(ui.spacing().interact_size.y);
|
||||
let (rect, response) = ui.allocate_at_least(desired_size, Sense::click());
|
||||
response.widget_info(|| {
|
||||
WidgetInfo::selected(WidgetType::SelectableLabel, selected, galley.text())
|
||||
WidgetInfo::selected(WidgetType::SelectableLabel, selected, text.text())
|
||||
});
|
||||
|
||||
let text_pos = ui
|
||||
.layout()
|
||||
.align_size_within_rect(galley.size(), rect.shrink2(button_padding))
|
||||
.align_size_within_rect(text.size(), rect.shrink2(button_padding))
|
||||
.min;
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
let text_color = ui
|
||||
.style()
|
||||
.visuals
|
||||
.override_text_color
|
||||
.unwrap_or_else(|| visuals.text_color());
|
||||
ui.painter().galley_with_color(text_pos, galley, text_color);
|
||||
text.paint_with_visuals(ui.painter(), text_pos, &visuals);
|
||||
response
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
#![allow(clippy::needless_pass_by_value)] // False positives with `impl ToString`
|
||||
|
||||
use crate::{widgets::Label, *};
|
||||
use std::ops::RangeInclusive;
|
||||
|
||||
use crate::*;
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
/// 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) {
|
||||
if !self.text.is_empty() {
|
||||
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);`"]
|
||||
pub struct TextEdit<'t> {
|
||||
text: &'t mut dyn TextBuffer,
|
||||
hint_text: String,
|
||||
hint_text: WidgetText,
|
||||
id: Option<Id>,
|
||||
id_source: Option<Id>,
|
||||
text_style: Option<TextStyle>,
|
||||
|
@ -127,9 +127,8 @@ impl<'t> TextEdit<'t> {
|
|||
}
|
||||
|
||||
/// 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 ToString) -> Self {
|
||||
self.hint_text = hint_text.to_string();
|
||||
pub fn hint_text(mut self, hint_text: impl Into<WidgetText>) -> Self {
|
||||
self.hint_text = hint_text.into();
|
||||
self
|
||||
}
|
||||
|
||||
|
@ -512,12 +511,12 @@ impl<'t> TextEdit<'t> {
|
|||
|
||||
if text.as_ref().is_empty() && !hint_text.is_empty() {
|
||||
let hint_text_color = ui.visuals().weak_text_color();
|
||||
let galley = ui.fonts().layout_job(if multiline {
|
||||
LayoutJob::simple(hint_text, text_style, hint_text_color, desired_size.x)
|
||||
let galley = if multiline {
|
||||
hint_text.into_galley(ui, Some(true), desired_size.x, text_style)
|
||||
} else {
|
||||
LayoutJob::simple_singleline(hint_text, text_style, hint_text_color)
|
||||
});
|
||||
painter.galley(response.rect.min, galley);
|
||||
hint_text.into_galley(ui, Some(false), f32::INFINITY, text_style)
|
||||
};
|
||||
galley.paint_with_fallback_color(&painter, response.rect.min, hint_text_color);
|
||||
}
|
||||
|
||||
if ui.memory().has_focus(id) {
|
||||
|
|
|
@ -42,13 +42,10 @@ impl super::View for FontBook {
|
|||
ui.horizontal_wrapped(|ui| {
|
||||
ui.spacing_mut().item_spacing.x = 0.0;
|
||||
ui.label("You can add more characters by installing additional fonts with ");
|
||||
ui.add(
|
||||
egui::Hyperlink::from_label_and_url(
|
||||
"Context::set_fonts",
|
||||
ui.add(egui::Hyperlink::from_label_and_url(
|
||||
egui::RichText::new("Context::set_fonts").text_style(egui::TextStyle::Monospace),
|
||||
"https://docs.rs/egui/latest/egui/struct.Context.html#method.set_fonts",
|
||||
)
|
||||
.text_style(egui::TextStyle::Monospace),
|
||||
);
|
||||
));
|
||||
ui.label(".");
|
||||
});
|
||||
|
||||
|
@ -90,10 +87,13 @@ impl super::View for FontBook {
|
|||
|
||||
for (&chr, name) in named_chars {
|
||||
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| {
|
||||
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));
|
||||
};
|
||||
|
||||
|
|
|
@ -142,7 +142,7 @@ impl Widgets {
|
|||
// 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.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.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.",
|
||||
|
@ -365,7 +365,7 @@ impl SubTree {
|
|||
) -> Action {
|
||||
if depth > 0
|
||||
&& ui
|
||||
.add(Button::new("delete").text_color(Color32::RED))
|
||||
.button(RichText::new("delete").color(Color32::RED))
|
||||
.clicked()
|
||||
{
|
||||
return Action::Delete;
|
||||
|
@ -565,12 +565,7 @@ fn text_layout_ui(ui: &mut egui::Ui) {
|
|||
},
|
||||
);
|
||||
|
||||
job.wrap_width = ui.available_width();
|
||||
|
||||
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.label(job);
|
||||
|
||||
ui.vertical_centered(|ui| {
|
||||
ui.add(crate::__egui_github_link_file_line!());
|
||||
|
|
|
@ -465,7 +465,7 @@ fn lorem_ipsum(ui: &mut egui::Ui, text: &str) {
|
|||
ui.with_layout(
|
||||
egui::Layout::top_down(egui::Align::LEFT).with_cross_justify(true),
|
||||
|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(
|
||||
egui::Layout::top_down(egui::Align::LEFT).with_cross_justify(true),
|
||||
|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) => {
|
||||
// This should only happen if the fetch API isn't available or something similar.
|
||||
ui.add(
|
||||
egui::Label::new(if error.is_empty() { "Error" } else { error })
|
||||
.text_color(egui::Color32::RED),
|
||||
ui.colored_label(
|
||||
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) => {
|
||||
ui.add(label_from_style(text, &style));
|
||||
ui.label(rich_text_from_style(text, &style));
|
||||
}
|
||||
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));
|
||||
}
|
||||
|
||||
|
@ -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 {
|
||||
heading,
|
||||
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 mut label = Label::new(text);
|
||||
let mut rich_text = RichText::new(text);
|
||||
if heading && !small {
|
||||
label = label.heading().strong();
|
||||
rich_text = rich_text.heading().strong();
|
||||
}
|
||||
if small && !heading {
|
||||
label = label.small();
|
||||
rich_text = rich_text.small();
|
||||
}
|
||||
if code {
|
||||
label = label.code();
|
||||
rich_text = rich_text.code();
|
||||
}
|
||||
if strong {
|
||||
label = label.strong();
|
||||
rich_text = rich_text.strong();
|
||||
} else if quoted {
|
||||
label = label.weak();
|
||||
rich_text = rich_text.weak();
|
||||
}
|
||||
if underline {
|
||||
label = label.underline();
|
||||
rich_text = rich_text.underline();
|
||||
}
|
||||
if strikethrough {
|
||||
label = label.strikethrough();
|
||||
rich_text = rich_text.strikethrough();
|
||||
}
|
||||
if italics {
|
||||
label = label.italics();
|
||||
rich_text = rich_text.italics();
|
||||
}
|
||||
if raised {
|
||||
label = label.raised();
|
||||
rich_text = rich_text.raised();
|
||||
}
|
||||
label
|
||||
rich_text
|
||||
}
|
||||
|
||||
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)")
|
||||
};
|
||||
($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)")
|
||||
};
|
||||
($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()
|
||||
);
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
/// 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 {
|
||||
&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)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
|
||||
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)]
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.sections.is_empty()
|
||||
|
@ -134,6 +152,15 @@ impl LayoutJob {
|
|||
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 {
|
||||
|
|
Loading…
Reference in a new issue