diff --git a/CHANGELOG.md b/CHANGELOG.md index 6f2356ed..687c5fa9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,7 @@ All notable changes to the egui crate will be documented in this file. -NOTE: [`eframe`](eframe/CHANGELOG.md), [`egui_web`](egui_web/CHANGELOG.md), [`egui-winit`](egui-winit/CHANGELOG.md) and [`egui_glium`](egui_glium/CHANGELOG.md) have their own changelogs! +NOTE: [`epaint`](epaint/CHANGELOG.md), [`eframe`](eframe/CHANGELOG.md), [`egui_web`](egui_web/CHANGELOG.md), [`egui-winit`](egui-winit/CHANGELOG.md) and [`egui_glium`](egui_glium/CHANGELOG.md) have their own changelogs! ## Unreleased @@ -10,7 +10,7 @@ NOTE: [`eframe`](eframe/CHANGELOG.md), [`egui_web`](egui_web/CHANGELOG.md), [`eg ### Added ⭐ * Add horizontal scrolling support to `ScrollArea` and `Window` (opt-in). * `TextEdit::layouter`: Add custom text layout for e.g. syntax highlighting or WYSIWYG. -* `Fonts::layout_job*`: New text layout engine allowing mixing fonts, colors and styles, with underlining and strikethrough. +* `Fonts::layout_job`: New text layout engine allowing mixing fonts, colors and styles, with underlining and strikethrough. * Add feature `"serialize"` separatedly from `"persistence"`. * Add `egui::widgets::global_dark_light_mode_buttons` to easily add buttons for switching the egui theme. * `TextEdit` can now be used to show text which can be selectedd and copied, but not edited. diff --git a/egui/src/context.rs b/egui/src/context.rs index dc0e21b4..b45fabbb 100644 --- a/egui/src/context.rs +++ b/egui/src/context.rs @@ -624,10 +624,14 @@ impl Context { /// Tessellate the given shapes into triangle meshes. pub fn tessellate(&self, shapes: Vec) -> Vec { + // A tempting optimization is to reuse the tessellation from last frame if the + // shapes are the same, but just comparing the shapes takes about 50% of the time + // it takes to tessellate them, so it is not a worth optimization. + let mut tessellation_options = self.memory().options.tessellation_options; tessellation_options.pixels_per_point = self.pixels_per_point(); tessellation_options.aa_size = 1.0 / self.pixels_per_point(); - let paint_stats = PaintStats::from_shapes(&shapes); // TODO: internal allocations + let paint_stats = PaintStats::from_shapes(&shapes); let clipped_meshes = tessellator::tessellate_shapes( shapes, tessellation_options, diff --git a/egui/src/lib.rs b/egui/src/lib.rs index 63d945a4..84d0e560 100644 --- a/egui/src/lib.rs +++ b/egui/src/lib.rs @@ -472,8 +472,8 @@ macro_rules! trace { // ---------------------------------------------------------------------------- -/// An assert that is only active when `egui` is compiled with the `egui_assert` feature -/// or with the `debug_egui_assert` feature in debug builds. +/// An assert that is only active when `egui` is compiled with the `extra_asserts` feature +/// or with the `extra_debug_asserts` feature in debug builds. #[macro_export] macro_rules! egui_assert { ($($arg: tt)*) => { diff --git a/egui/src/response.rs b/egui/src/response.rs index f2c08050..bcdc49d2 100644 --- a/egui/src/response.rs +++ b/egui/src/response.rs @@ -408,7 +408,7 @@ impl Response { /// Check for more interactions (e.g. sense clicks on a `Response` returned from a label). /// /// Note that this call will not add any hover-effects to the widget, so when possible - /// it is better to give the widget a `Sense` instead, e.g. using `[Label::sense]`. + /// it is better to give the widget a `Sense` instead, e.g. using [`crate::Label::sense`]. /// /// ``` /// # let mut ui = egui::Ui::__test(); diff --git a/egui/src/widgets/mod.rs b/egui/src/widgets/mod.rs index 1bdeb7a5..22f65716 100644 --- a/egui/src/widgets/mod.rs +++ b/egui/src/widgets/mod.rs @@ -19,18 +19,22 @@ mod separator; mod slider; pub(crate) mod text_edit; +pub use button::*; +pub use drag_value::DragValue; pub use hyperlink::*; +pub use image::Image; pub use label::*; pub use progress_bar::ProgressBar; -pub use selected_label::*; -pub use separator::*; -pub use {button::*, drag_value::DragValue, image::Image, slider::*, text_edit::*}; +pub use selected_label::SelectableLabel; +pub use separator::Separator; +pub use slider::*; +pub use text_edit::*; // ---------------------------------------------------------------------------- /// Anything implementing Widget can be added to a [`Ui`] with [`Ui::add`]. /// -/// `[Button]`, `[Label]`, [`Slider`], etc all implement the `Widget` trait. +/// [`Button`], [`Label`], [`Slider`], etc all implement the `Widget` trait. /// /// Note that the widgets (`Button`, `TextEdit` etc) are /// [builders](https://doc.rust-lang.org/1.0.0/style/ownership/builders.html), diff --git a/emath/Cargo.toml b/emath/Cargo.toml index 7cfe6c75..62e25dee 100644 --- a/emath/Cargo.toml +++ b/emath/Cargo.toml @@ -24,9 +24,9 @@ all-features = true [dependencies] # Add compatability with https://github.com/kvark/mint +bytemuck = { version = "1.7.2", features = ["derive"], optional = true } mint = { version = "0.5.6", optional = true } serde = { version = "1", features = ["derive"], optional = true } -bytemuck = { version = "1.7.2", features = ["derive"], optional = true } [features] default = [] diff --git a/emath/src/lib.rs b/emath/src/lib.rs index 315d7a44..4cce73d0 100644 --- a/emath/src/lib.rs +++ b/emath/src/lib.rs @@ -358,8 +358,8 @@ fn test_normalized_angle() { // ---------------------------------------------------------------------------- -/// An assert that is only active when `egui` is compiled with the `egui_assert` feature -/// or with the `debug_egui_assert` feature in debug builds. +/// An assert that is only active when `emath` is compiled with the `extra_asserts` feature +/// or with the `extra_debug_asserts` feature in debug builds. #[macro_export] macro_rules! emath_assert { ($($arg: tt)*) => { diff --git a/epaint/CHANGELOG.md b/epaint/CHANGELOG.md new file mode 100644 index 00000000..74136157 --- /dev/null +++ b/epaint/CHANGELOG.md @@ -0,0 +1,9 @@ +# epaint changelog + +All notable changes to the epaint crate will be documented in this file. + +## Unreleased +* `Fonts::layout_job`: New text layout engine allowing mixing fonts, colors and styles, with underlining and strikethrough. +* New `CircleShape`, `PathShape`, `RectShape` and `TextShape` used in `enum Shape`. +* Add support for rotated text (see `TextShape`). +* Added `"convert_bytemuck"` feature. diff --git a/epaint/Cargo.toml b/epaint/Cargo.toml index 60cc7a8f..dd95ad57 100644 --- a/epaint/Cargo.toml +++ b/epaint/Cargo.toml @@ -30,11 +30,11 @@ emath = { version = "0.14.0", path = "../emath" } ab_glyph = "0.2.11" ahash = { version = "0.7", features = ["std"], default-features = false } atomic_refcell = { version = "0.1", optional = true } # Used instead of parking_lot when you are always using epaint in a single thread. About as fast as parking_lot. Panics on multi-threaded use. +bytemuck = { version = "1.7.2", features = ["derive"], optional = true } cint = { version = "^0.2.2", optional = true } nohash-hasher = "0.2" parking_lot = { version = "0.11", optional = true } # Using parking_lot over std::sync::Mutex gives 50% speedups in some real-world scenarios. serde = { version = "1", features = ["derive"], optional = true } -bytemuck = { version = "1.7.2", features = ["derive"], optional = true } [features] default = ["default_fonts", "multi_threaded"] diff --git a/epaint/src/lib.rs b/epaint/src/lib.rs index 8ed2edfc..fb2ed055 100644 --- a/epaint/src/lib.rs +++ b/epaint/src/lib.rs @@ -1,4 +1,9 @@ -//! 2D graphics/rendering. Fonts, textures, color, geometry, tessellation etc. +//! A simple 2D graphics library for turning simple 2D shapes and text into textured triangles. +//! +//! Made for [`egui`](https://github.com/emilk/egui/). +//! +//! Create some [`Shape`]:s and pass them to [`tessellate_shapes`] to generate [`Mesh`]:es +//! that you can then paint using some graphics API of your choice (e.g. OpenGL). // Forbid warnings in release builds: #![cfg_attr(not(debug_assertions), deny(warnings))] @@ -91,8 +96,8 @@ pub use { shape::{CircleShape, PathShape, RectShape, Shape, TextShape}, stats::PaintStats, stroke::Stroke, - tessellator::{TessellationOptions, Tessellator}, - text::{Galley, TextStyle}, + tessellator::{tessellate_shapes, TessellationOptions, Tessellator}, + text::{Fonts, Galley, TextStyle}, texture_atlas::{Texture, TextureAtlas}, }; @@ -129,7 +134,7 @@ impl Default for TextureId { /// A [`Shape`] within a clip rectangle. /// /// Everything is using logical points. -#[derive(Clone, Debug)] +#[derive(Clone, Debug, PartialEq)] pub struct ClippedShape( /// Clip / scissor rectangle. /// Only show the part of the [`Shape`] that falls within this. @@ -153,8 +158,8 @@ pub struct ClippedMesh( // ---------------------------------------------------------------------------- -/// An assert that is only active when `egui` is compiled with the `egui_assert` feature -/// or with the `debug_egui_assert` feature in debug builds. +/// An assert that is only active when `epaint` is compiled with the `extra_asserts` feature +/// or with the `extra_debug_asserts` feature in debug builds. #[macro_export] macro_rules! epaint_assert { ($($arg: tt)*) => { diff --git a/epaint/src/shape.rs b/epaint/src/shape.rs index cde0c1a9..dba2b243 100644 --- a/epaint/src/shape.rs +++ b/epaint/src/shape.rs @@ -123,6 +123,57 @@ impl Shape { pub fn galley(pos: Pos2, galley: std::sync::Arc) -> Self { TextShape::new(pos, galley).into() } + + pub fn mesh(mesh: Mesh) -> Self { + crate::epaint_assert!(mesh.is_valid()); + Self::Mesh(mesh) + } +} + +/// ## Inspection and transforms +impl Shape { + #[inline(always)] + pub fn texture_id(&self) -> super::TextureId { + if let Shape::Mesh(mesh) = self { + mesh.texture_id + } else { + super::TextureId::Egui + } + } + + /// Move the shape by this many points, in-place. + pub fn translate(&mut self, delta: Vec2) { + match self { + Shape::Noop => {} + Shape::Vec(shapes) => { + for shape in shapes { + shape.translate(delta); + } + } + Shape::Circle(circle_shape) => { + circle_shape.center += delta; + } + Shape::LineSegment { points, .. } => { + for p in points { + *p += delta; + } + } + Shape::Path(path_shape) => { + for p in &mut path_shape.points { + *p += delta; + } + } + Shape::Rect(rect_shape) => { + rect_shape.rect = rect_shape.rect.translate(delta); + } + Shape::Text(text_shape) => { + text_shape.pos += delta; + } + Shape::Mesh(mesh) => { + mesh.translate(delta); + } + } + } } // ---------------------------------------------------------------------------- @@ -398,54 +449,3 @@ fn dashes_from_line( position_on_segment -= segment_length; }); } - -/// ## Operations -impl Shape { - pub fn mesh(mesh: Mesh) -> Self { - crate::epaint_assert!(mesh.is_valid()); - Self::Mesh(mesh) - } - - #[inline(always)] - pub fn texture_id(&self) -> super::TextureId { - if let Shape::Mesh(mesh) = self { - mesh.texture_id - } else { - super::TextureId::Egui - } - } - - /// Translate location by this much, in-place - pub fn translate(&mut self, delta: Vec2) { - match self { - Shape::Noop => {} - Shape::Vec(shapes) => { - for shape in shapes { - shape.translate(delta); - } - } - Shape::Circle(circle_shape) => { - circle_shape.center += delta; - } - Shape::LineSegment { points, .. } => { - for p in points { - *p += delta; - } - } - Shape::Path(path_shape) => { - for p in &mut path_shape.points { - *p += delta; - } - } - Shape::Rect(rect_shape) => { - rect_shape.rect = rect_shape.rect.translate(delta); - } - Shape::Text(text_shape) => { - text_shape.pos += delta; - } - Shape::Mesh(mesh) => { - mesh.translate(delta); - } - } - } -} diff --git a/epaint/src/tessellator.rs b/epaint/src/tessellator.rs index 78b22993..368971b7 100644 --- a/epaint/src/tessellator.rs +++ b/epaint/src/tessellator.rs @@ -257,21 +257,30 @@ pub enum PathType { #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] #[cfg_attr(feature = "serde", serde(default))] pub struct TessellationOptions { - /// Size of a point in pixels, e.g. 2.0. Used to snap text to pixel boundaries. + /// Size of a point in pixels (DPI scaling), e.g. 2.0. Used to snap text to pixel boundaries. pub pixels_per_point: f32, - /// Size of a pixel in points, e.g. 0.5, or larger if you want more blurry edges. + + /// The size of a pixel (in points), used for anti-aliasing (smoothing of edges). + /// This is normally the inverse of [`Self::pixels_per_point`], + /// but you can make it larger if you want more blurry edges. pub aa_size: f32, + /// Anti-aliasing makes shapes appear smoother, but requires more triangles and is therefore slower. /// This setting does not affect text. /// Default: `true`. pub anti_alias: bool, - /// If `true` (default) cull certain primitives before tessellating them + + /// If `true` (default) cull certain primitives before tessellating them. + /// This likely makes pub coarse_tessellation_culling: bool, - /// Output the clip rectangles to be painted? + + /// Output the clip rectangles to be painted. pub debug_paint_clip_rects: bool, - /// Output the text-containing rectangles + + /// Output the text-containing rectangles. pub debug_paint_text_rects: bool, - /// If true, no clipping will be done + + /// If true, no clipping will be done. pub debug_ignore_clip_rects: bool, } @@ -503,7 +512,11 @@ fn mul_color(color: Color32, factor: f32) -> Color32 { // ---------------------------------------------------------------------------- -/// Converts [`Shape`]s into [`Mesh`]. +/// Converts [`Shape`]s into triangles ([`Mesh`]). +/// +/// For performance reasons it is smart to reuse the same `Tessellator`. +/// +/// Se also [`tessellate_shapes`], a convenient wrapper around [`Tessellator`]. pub struct Tessellator { options: TessellationOptions, /// Only used for culling @@ -513,6 +526,7 @@ pub struct Tessellator { } impl Tessellator { + /// Create a new [`Tessellator`]. pub fn from_options(options: TessellationOptions) -> Self { Self { options, @@ -524,12 +538,9 @@ impl Tessellator { /// Tessellate a single [`Shape`] into a [`Mesh`]. /// - /// * `shape`: the shape to tessellate - /// * `options`: tessellation quality - /// * `tex_size`: size of the font texture (required to normalize glyph uv rectangles) - /// * `out`: where the triangles are put - /// * `scratchpad_path`: if you plan to run `tessellate_shape` - /// many times, pass it a reference to the same `Path` to avoid excessive allocations. + /// * `tex_size`: size of the font texture (required to normalize glyph uv rectangles). + /// * `shape`: the shape to tessellate. + /// * `out`: triangles are appended to this. pub fn tessellate_shape(&mut self, tex_size: [usize; 2], shape: Shape, out: &mut Mesh) { let clip_rect = self.clip_rect; let options = &self.options; @@ -776,13 +787,15 @@ impl Tessellator { /// Turns [`Shape`]:s into sets of triangles. /// -/// The given shapes will be painted back-to-front (painters algorithm). +/// The given shapes will tessellated in the same order as they are given. /// They will be batched together by clip rectangle. /// -/// * `shapes`: the shape to tessellate +/// * `shapes`: what to tessellate /// * `options`: tessellation quality /// * `tex_size`: size of the font texture (required to normalize glyph uv rectangles) /// +/// The implementation uses a [`Tessellator`]. +/// /// ## Returns /// A list of clip rectangles with matching [`Mesh`]. pub fn tessellate_shapes( diff --git a/epaint/src/text/fonts.rs b/epaint/src/text/fonts.rs index c78b98f7..515470ca 100644 --- a/epaint/src/text/fonts.rs +++ b/epaint/src/text/fonts.rs @@ -201,6 +201,8 @@ impl Default for FontDefinitions { } /// The collection of fonts used by `epaint`. +/// +/// Required in order to paint text. pub struct Fonts { pixels_per_point: f32, definitions: FontDefinitions, @@ -214,6 +216,8 @@ pub struct Fonts { } impl Fonts { + /// Create a new [`Fonts`] for text layout. + /// This call is expensive, so only create on [`Fonts`] and then reuse it. pub fn new(pixels_per_point: f32, definitions: FontDefinitions) -> Self { assert!( 0.0 < pixels_per_point && pixels_per_point < 100.0, diff --git a/epaint/src/text/text_layout_types.rs b/epaint/src/text/text_layout_types.rs index c9a49dec..26ce416d 100644 --- a/epaint/src/text/text_layout_types.rs +++ b/epaint/src/text/text_layout_types.rs @@ -11,12 +11,37 @@ use emath::*; /// /// This supports mixing different fonts, color and formats (underline etc). /// -/// Pass this to [`Fonts::layout_job]` or [`crate::text::layout`]. +/// Pass this to [`crate::Fonts::layout_job`] or [`crate::text::layout`]. +/// +/// ## Example: +/// ``` +/// use epaint::{Color32, text::{LayoutJob, TextFormat}, TextStyle}; +/// +/// let mut job = LayoutJob::default(); +/// job.append( +/// "Hello ", +/// 0.0, +/// TextFormat { +/// style: TextStyle::Body, +/// color: Color32::WHITE, +/// ..Default::default() +/// }, +/// ); +/// job.append( +/// "World!", +/// 0.0, +/// TextFormat { +/// style: TextStyle::Monospace, +/// color: Color32::BLACK, +/// ..Default::default() +/// }, +/// ); +/// ``` #[derive(Clone, Debug, PartialEq)] #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] pub struct LayoutJob { /// The complete text of this job, referenced by `LayoutSection`. - pub text: String, // TODO: Cow<'static, str> + pub text: String, /// The different section, which can have different fonts, colors, etc. pub sections: Vec, @@ -206,6 +231,9 @@ impl TextFormat { // ---------------------------------------------------------------------------- +/// Text that has been layed out, ready for painting. +/// +/// You can create a [`Galley`] using [`crate::Fonts::layout_job`]; #[derive(Clone, Debug, PartialEq)] pub struct Galley { /// The job that this galley is the result of. diff --git a/epi/src/lib.rs b/epi/src/lib.rs index e50bfb5f..fe0db566 100644 --- a/epi/src/lib.rs +++ b/epi/src/lib.rs @@ -91,8 +91,8 @@ pub trait App { /// Called once before the first frame. /// - /// Allows you to do setup code, e.g to call `[egui::Context::set_fonts]`, - /// `[egui::Context::set_visuals]` etc. + /// Allows you to do setup code, e.g to call [`egui::Context::set_fonts`], + /// [`egui::Context::set_visuals`] etc. /// /// Also allows you to restore state, if there is a storage (required the "persistence" feature). fn setup( @@ -106,6 +106,9 @@ pub trait App { /// If `true` a warm-up call to [`Self::update`] will be issued where /// `ctx.memory().everything_is_visible()` will be set to `true`. /// + /// This will help pre-caching all text, preventing stutter when + /// opening a window containing new glyphs. + /// /// In this warm-up call, all painted shapes will be ignored. fn warm_up_enabled(&self) -> bool { false