diff --git a/.github/workflows/labels.yml b/.github/workflows/labels.yml index 193ac9134..f2252a31e 100644 --- a/.github/workflows/labels.yml +++ b/.github/workflows/labels.yml @@ -29,4 +29,4 @@ jobs: with: mode: minimum count: 1 - labels: "CI, dependencies, docs and examples, ecolor, eframe, egui_extras, egui_glow, egui-wgpu, egui-winit, egui, epaint, plot, typo" + labels: "CI, dependencies, docs and examples, ecolor, eframe, egui_extras, egui_glow, egui_plot, egui-wgpu, egui-winit, egui, epaint, typo" diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md index 71d2d1ae5..d3f6e10e7 100644 --- a/ARCHITECTURE.md +++ b/ARCHITECTURE.md @@ -5,7 +5,7 @@ Also see [`CONTRIBUTING.md`](CONTRIBUTING.md) for what to do before opening a PR ## Crate overview -The crates in this repository are: `egui, emath, epaint, egui_extras, egui-winit, egui_glium, egui_glow, egui_demo_lib, egui_demo_app`. +The crates in this repository are: `egui, emath, epaint, egui_extras, egui_plot, egui-winit, egui_glium, egui_glow, egui_demo_lib, egui_demo_app`. ### `egui`: The main GUI library. Example code: `if ui.button("Click me").clicked() { … }` @@ -24,6 +24,9 @@ Depends on `emath`. ### `egui_extras` This adds additional features on top of `egui`. +### `egui_plot` +Plotting for `egui`. + ### `egui-winit` This crates provides bindings between [`egui`](https://github.com/emilk/egui) and [winit](https://crates.io/crates/winit). diff --git a/CHANGELOG.md b/CHANGELOG.md index fc129dd8e..ae62045fa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,7 +1,7 @@ # egui changelog All notable changes to the `egui` crate will be documented in this file. -NOTE: [`epaint`](crates/epaint/CHANGELOG.md), [`eframe`](crates/eframe/CHANGELOG.md), [`egui-winit`](crates/egui-winit/CHANGELOG.md), [`egui_glium`](crates/egui_glium/CHANGELOG.md), [`egui_glow`](crates/egui_glow/CHANGELOG.md) and [`egui-wgpu`](crates/egui-wgpu/CHANGELOG.md) have their own changelogs! +NOTE: [`epaint`](crates/epaint/CHANGELOG.md), [`egui_plot`](crates/egui_plot/CHANGELOG.md), [`eframe`](crates/eframe/CHANGELOG.md), [`egui-winit`](crates/egui-winit/CHANGELOG.md), [`egui_glium`](crates/egui_glium/CHANGELOG.md), [`egui_glow`](crates/egui_glow/CHANGELOG.md) and [`egui-wgpu`](crates/egui-wgpu/CHANGELOG.md) have their own changelogs! This file is updated upon each release. Changes since the last release can be found by running the `scripts/generate_changelog.py` script. diff --git a/Cargo.lock b/Cargo.lock index a83d04c3f..746c04612 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1247,6 +1247,7 @@ dependencies = [ "document-features", "egui", "egui_extras", + "egui_plot", "enum-map", "log", "serde", @@ -1288,6 +1289,15 @@ dependencies = [ "web-sys", ] +[[package]] +name = "egui_plot" +version = "0.22.0" +dependencies = [ + "document-features", + "egui", + "serde", +] + [[package]] name = "ehttp" version = "0.2.0" @@ -3129,6 +3139,7 @@ name = "save_plot" version = "0.1.0" dependencies = [ "eframe", + "egui_plot", "env_logger", "image", "rfd", diff --git a/Cargo.toml b/Cargo.toml index c98179f8f..bda52fdaf 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,6 +6,7 @@ members = [ "crates/egui_demo_lib", "crates/egui_extras", "crates/egui_glow", + "crates/egui_plot", "crates/egui-wgpu", "crates/egui-winit", "crates/egui", diff --git a/crates/eframe/src/web/events.rs b/crates/eframe/src/web/events.rs index f44adb692..cbfa11a68 100644 --- a/crates/eframe/src/web/events.rs +++ b/crates/eframe/src/web/events.rs @@ -473,6 +473,7 @@ pub fn install_canvas_events(runner_ref: &WebRunner) -> Result<(), JsValue> { for i in 0..files.length() { if let Some(file) = files.get(i) { let name = file.name(); + let mime = file.type_(); let last_modified = std::time::UNIX_EPOCH + std::time::Duration::from_millis(file.last_modified() as u64); @@ -491,6 +492,7 @@ pub fn install_canvas_events(runner_ref: &WebRunner) -> Result<(), JsValue> { runner_lock.input.raw.dropped_files.push( egui::DroppedFile { name, + mime, last_modified: Some(last_modified), bytes: Some(bytes.into()), ..Default::default() diff --git a/crates/egui-wgpu/src/renderer.rs b/crates/egui-wgpu/src/renderer.rs index 94fb95307..f44b8d46f 100644 --- a/crates/egui-wgpu/src/renderer.rs +++ b/crates/egui-wgpu/src/renderer.rs @@ -822,7 +822,7 @@ impl Renderer { ) .expect("Failed to create staging buffer for index data"); let mut index_offset = 0; - for epaint::ClippedPrimitive { primitive, .. } in paint_jobs.iter() { + for epaint::ClippedPrimitive { primitive, .. } in paint_jobs { match primitive { Primitive::Mesh(mesh) => { let size = mesh.indices.len() * std::mem::size_of::(); @@ -857,7 +857,7 @@ impl Renderer { ) .expect("Failed to create staging buffer for vertex data"); let mut vertex_offset = 0; - for epaint::ClippedPrimitive { primitive, .. } in paint_jobs.iter() { + for epaint::ClippedPrimitive { primitive, .. } in paint_jobs { match primitive { Primitive::Mesh(mesh) => { let size = mesh.vertices.len() * std::mem::size_of::(); diff --git a/crates/egui-winit/src/window_settings.rs b/crates/egui-winit/src/window_settings.rs index 17c5c9d94..94de38c48 100644 --- a/crates/egui-winit/src/window_settings.rs +++ b/crates/egui-winit/src/window_settings.rs @@ -109,7 +109,9 @@ impl WindowSettings { return; } - let Some(inner_size_points) = self.inner_size_points else { return; }; + let Some(inner_size_points) = self.inner_size_points else { + return; + }; if let Some(pos_px) = &mut self.inner_position_pixels { clamp_pos_to_monitors(event_loop, inner_size_points, pos_px); diff --git a/crates/egui/src/data/input.rs b/crates/egui/src/data/input.rs index 643f9148b..9837c86bc 100644 --- a/crates/egui/src/data/input.rs +++ b/crates/egui/src/data/input.rs @@ -176,6 +176,9 @@ pub struct DroppedFile { /// Name of the file. Set by the `eframe` web backend. pub name: String, + /// With the `eframe` web backend, this is set to the mime-type of the file (if available). + pub mime: String, + /// Set by the `eframe` web backend. pub last_modified: Option, diff --git a/crates/egui/src/grid.rs b/crates/egui/src/grid.rs index acd5c3576..3f1dc592c 100644 --- a/crates/egui/src/grid.rs +++ b/crates/egui/src/grid.rs @@ -220,9 +220,15 @@ impl GridLayout { fn paint_row(&mut self, cursor: &mut Rect, painter: &Painter) { // handle row color painting based on color-picker function - let Some(color_picker) = self.color_picker.as_ref() else { return }; - let Some(row_color) = color_picker(self.row, &self.style) else { return }; - let Some(height) = self.prev_state.row_height(self.row) else {return }; + let Some(color_picker) = self.color_picker.as_ref() else { + return; + }; + let Some(row_color) = color_picker(self.row, &self.style) else { + return; + }; + let Some(height) = self.prev_state.row_height(self.row) else { + return; + }; // Paint background for coming row: let size = Vec2::new(self.prev_state.full_width(self.spacing.x), height); let rect = Rect::from_min_size(cursor.min, size); diff --git a/crates/egui/src/input_state.rs b/crates/egui/src/input_state.rs index c357f0adf..606864bcb 100644 --- a/crates/egui/src/input_state.rs +++ b/crates/egui/src/input_state.rs @@ -772,7 +772,7 @@ impl PointerState { /// Latest reported pointer position. /// When tapping a touch screen, this will be `None`. #[inline(always)] - pub(crate) fn latest_pos(&self) -> Option { + pub fn latest_pos(&self) -> Option { self.latest_pos } diff --git a/crates/egui/src/lib.rs b/crates/egui/src/lib.rs index 60b8d995f..afcbae971 100644 --- a/crates/egui/src/lib.rs +++ b/crates/egui/src/lib.rs @@ -329,6 +329,8 @@ pub mod widgets; #[cfg(feature = "accesskit")] pub use accesskit; +pub use ahash; + pub use epaint; pub use epaint::ecolor; pub use epaint::emath; diff --git a/crates/egui/src/ui.rs b/crates/egui/src/ui.rs index e29a40097..9f4135025 100644 --- a/crates/egui/src/ui.rs +++ b/crates/egui/src/ui.rs @@ -809,7 +809,8 @@ impl Ui { self.interact(rect, id, sense) } - pub(crate) fn advance_cursor_after_rect(&mut self, rect: Rect) -> Id { + /// Allocate a rect without interacting with it. + pub fn advance_cursor_after_rect(&mut self, rect: Rect) -> Id { egui_assert!(!rect.any_nan()); let item_spacing = self.spacing().item_spacing; self.placer.advance_after_rects(rect, rect, item_spacing); diff --git a/crates/egui/src/widgets/mod.rs b/crates/egui/src/widgets/mod.rs index 5cfe1816f..921848f89 100644 --- a/crates/egui/src/widgets/mod.rs +++ b/crates/egui/src/widgets/mod.rs @@ -12,7 +12,6 @@ pub(crate) mod drag_value; mod hyperlink; mod image; mod label; -pub mod plot; mod progress_bar; mod selected_label; mod separator; diff --git a/crates/egui/src/widgets/progress_bar.rs b/crates/egui/src/widgets/progress_bar.rs index ef6d16c3d..239069595 100644 --- a/crates/egui/src/widgets/progress_bar.rs +++ b/crates/egui/src/widgets/progress_bar.rs @@ -12,6 +12,7 @@ enum ProgressBarText { pub struct ProgressBar { progress: f32, desired_width: Option, + desired_height: Option, text: Option, fill: Option, animate: bool, @@ -23,6 +24,7 @@ impl ProgressBar { Self { progress: progress.clamp(0.0, 1.0), desired_width: None, + desired_height: None, text: None, fill: None, animate: false, @@ -35,6 +37,12 @@ impl ProgressBar { self } + /// The desired height of the bar. Will use the default interaction size if not set. + pub fn desired_height(mut self, desired_height: f32) -> Self { + self.desired_height = Some(desired_height); + self + } + /// The fill color of the bar. pub fn fill(mut self, color: Color32) -> Self { self.fill = Some(color); @@ -67,6 +75,7 @@ impl Widget for ProgressBar { let ProgressBar { progress, desired_width, + desired_height, text, fill, animate, @@ -76,7 +85,7 @@ impl Widget for ProgressBar { let desired_width = desired_width.unwrap_or_else(|| ui.available_size_before_wrap().x.at_least(96.0)); - let height = ui.spacing().interact_size.y; + let height = desired_height.unwrap_or(ui.spacing().interact_size.y); let (outer_rect, response) = ui.allocate_exact_size(vec2(desired_width, height), Sense::hover()); diff --git a/crates/egui_demo_app/src/wrap_app.rs b/crates/egui_demo_app/src/wrap_app.rs index 169f48a19..9d597ee4c 100644 --- a/crates/egui_demo_app/src/wrap_app.rs +++ b/crates/egui_demo_app/src/wrap_app.rs @@ -513,9 +513,18 @@ impl WrapApp { } else { "???".to_owned() }; - if let Some(bytes) = &file.bytes { - write!(info, " ({} bytes)", bytes.len()).ok(); + + let mut additional_info = vec![]; + if !file.mime.is_empty() { + additional_info.push(format!("type: {}", file.mime)); } + if let Some(bytes) = &file.bytes { + additional_info.push(format!("{} bytes", bytes.len())); + } + if !additional_info.is_empty() { + info += &format!(" ({})", additional_info.join(", ")); + } + ui.label(info); } }); diff --git a/crates/egui_demo_lib/Cargo.toml b/crates/egui_demo_lib/Cargo.toml index e86baca29..f643c0cb7 100644 --- a/crates/egui_demo_lib/Cargo.toml +++ b/crates/egui_demo_lib/Cargo.toml @@ -23,8 +23,10 @@ all-features = true default = [] chrono = ["egui_extras/datepicker", "dep:chrono"] + ## Allow serialization using [`serde`](https://docs.rs/serde). -serde = ["egui/serde", "dep:serde"] +serde = ["egui/serde", "egui_plot/serde", "dep:serde"] + ## Enable better syntax highlighting using [`syntect`](https://docs.rs/syntect). syntax_highlighting = ["syntect"] @@ -32,6 +34,7 @@ syntax_highlighting = ["syntect"] [dependencies] egui = { version = "0.22.0", path = "../egui", default-features = false } egui_extras = { version = "0.22.0", path = "../egui_extras" } +egui_plot = { version = "0.22.0", path = "../egui_plot" } enum-map = { version = "2", features = ["serde"] } log = { version = "0.4", features = ["std"] } unicode_names2 = { version = "0.6.0", default-features = false } diff --git a/crates/egui_demo_lib/src/demo/context_menu.rs b/crates/egui_demo_lib/src/demo/context_menu.rs index 1e1487ceb..96375d72e 100644 --- a/crates/egui_demo_lib/src/demo/context_menu.rs +++ b/crates/egui_demo_lib/src/demo/context_menu.rs @@ -122,7 +122,7 @@ impl super::View for ContextMenus { impl ContextMenus { fn example_plot(&self, ui: &mut egui::Ui) -> egui::Response { - use egui::plot::{Line, PlotPoints}; + use egui_plot::{Line, PlotPoints}; let n = 128; let line = Line::new( (0..=n) @@ -137,7 +137,7 @@ impl ContextMenus { }) .collect::(), ); - egui::plot::Plot::new("example_plot") + egui_plot::Plot::new("example_plot") .show_axes(self.show_axes) .allow_drag(self.allow_drag) .allow_zoom(self.allow_zoom) diff --git a/crates/egui_demo_lib/src/demo/plot_demo.rs b/crates/egui_demo_lib/src/demo/plot_demo.rs index c27117922..cd742c1a1 100644 --- a/crates/egui_demo_lib/src/demo/plot_demo.rs +++ b/crates/egui_demo_lib/src/demo/plot_demo.rs @@ -3,7 +3,7 @@ use std::ops::RangeInclusive; use egui::*; -use egui::plot::{ +use egui_plot::{ Arrows, AxisBools, AxisHints, Bar, BarChart, BoxElem, BoxPlot, BoxSpread, CoordinatesFormatter, Corner, GridInput, GridMark, HLine, Legend, Line, LineStyle, MarkerShape, Plot, PlotImage, PlotPoint, PlotPoints, PlotResponse, Points, Polygon, Text, VLine, @@ -575,7 +575,7 @@ impl CustomAxesDemo { .max_digits(4), AxisHints::default() .label("Absolute") - .placement(plot::HPlacement::Right), + .placement(egui_plot::HPlacement::Right), ]; Plot::new("custom_axes") .data_aspect(2.0 * MINS_PER_DAY as f32) @@ -636,7 +636,7 @@ impl LinkedAxesDemo { )) } - fn configure_plot(plot_ui: &mut plot::PlotUi) { + fn configure_plot(plot_ui: &mut egui_plot::PlotUi) { plot_ui.line(LinkedAxesDemo::line_with_slope(0.5)); plot_ui.line(LinkedAxesDemo::line_with_slope(1.0)); plot_ui.line(LinkedAxesDemo::line_with_slope(2.0)); @@ -671,7 +671,7 @@ impl LinkedAxesDemo { .height(250.0) .y_axis_width(3) .y_axis_label("y") - .y_axis_position(plot::HPlacement::Right) + .y_axis_position(egui_plot::HPlacement::Right) .link_axis(link_group_id, self.link_x, self.link_y) .link_cursor(link_group_id, self.link_cursor_x, self.link_cursor_y) .show(ui, LinkedAxesDemo::configure_plot); diff --git a/crates/egui_demo_lib/src/demo/widget_gallery.rs b/crates/egui_demo_lib/src/demo/widget_gallery.rs index 61d19fe2e..a2e7b187b 100644 --- a/crates/egui_demo_lib/src/demo/widget_gallery.rs +++ b/crates/egui_demo_lib/src/demo/widget_gallery.rs @@ -258,7 +258,7 @@ impl WidgetGallery { } fn example_plot(ui: &mut egui::Ui) -> egui::Response { - use egui::plot::{Line, PlotPoints}; + use egui_plot::{Line, PlotPoints}; let n = 128; let line_points: PlotPoints = (0..=n) .map(|i| { @@ -268,7 +268,7 @@ fn example_plot(ui: &mut egui::Ui) -> egui::Response { }) .collect(); let line = Line::new(line_points); - egui::plot::Plot::new("example_plot") + egui_plot::Plot::new("example_plot") .height(32.0) .show_axes(false) .data_aspect(1.0) diff --git a/crates/egui_extras/src/datepicker/mod.rs b/crates/egui_extras/src/datepicker/mod.rs index d1e10f1cc..33038d763 100644 --- a/crates/egui_extras/src/datepicker/mod.rs +++ b/crates/egui_extras/src/datepicker/mod.rs @@ -24,7 +24,7 @@ fn month_data(year: i32, month: u32) -> Vec { if start.weekday() == Weekday::Sun { weeks.push(Week { number: start.iso_week().week() as u8, - days: week.drain(..).collect(), + days: std::mem::take(&mut week), }); } start = start.checked_add_signed(Duration::days(1)).unwrap(); diff --git a/crates/egui_plot/CHANGELOG.md b/crates/egui_plot/CHANGELOG.md new file mode 100644 index 000000000..5f3b7cbc9 --- /dev/null +++ b/crates/egui_plot/CHANGELOG.md @@ -0,0 +1,5 @@ +# Changelog for egui_plot +All notable changes to the `egui_plot` integration will be noted in this file. + +This file is updated upon each release. +Changes since the last release can be found by running the `scripts/generate_changelog.py` script. diff --git a/crates/egui_plot/Cargo.toml b/crates/egui_plot/Cargo.toml new file mode 100644 index 000000000..152479db8 --- /dev/null +++ b/crates/egui_plot/Cargo.toml @@ -0,0 +1,42 @@ +[package] +name = "egui_plot" +version = "0.22.0" +authors = [ + "Dominik Rössler ", + "Emil Ernerfeldt ", + "René Rössler ", +] +description = "Immediate mode plotting for the egui GUI library" +edition = "2021" +rust-version = "1.67" +homepage = "https://github.com/emilk/egui" +license = "MIT OR Apache-2.0" +readme = "README.md" +repository = "https://github.com/emilk/egui" +categories = ["visualization", "gui"] +keywords = ["egui", "plot", "plotting"] +include = ["../LICENSE-APACHE", "../LICENSE-MIT", "**/*.rs", "Cargo.toml"] + +[package.metadata.docs.rs] +all-features = true + +[lib] + + +[features] +default = [] + + +## Allow serialization using [`serde`](https://docs.rs/serde). +serde = ["dep:serde", "egui/serde"] + + +[dependencies] +egui = { version = "0.22.0", path = "../egui", default-features = false } + + +#! ### Optional dependencies +## Enable this when generating docs. +document-features = { version = "0.2", optional = true } + +serde = { version = "1", optional = true, features = ["derive"] } diff --git a/crates/egui_plot/README.md b/crates/egui_plot/README.md new file mode 100644 index 000000000..0715f0395 --- /dev/null +++ b/crates/egui_plot/README.md @@ -0,0 +1,9 @@ +# egui_plot + +[![Latest version](https://img.shields.io/crates/v/egui_plot.svg)](https://crates.io/crates/egui_plot) +[![Documentation](https://docs.rs/egui_plot/badge.svg)](https://docs.rs/egui_plot) +[![unsafe forbidden](https://img.shields.io/badge/unsafe-forbidden-success.svg)](https://github.com/rust-secure-code/safety-dance/) +![MIT](https://img.shields.io/badge/license-MIT-blue.svg) +![Apache](https://img.shields.io/badge/license-Apache-blue.svg) + +Immediate mode plotting for [`egui`](https://github.com/emilk/egui). diff --git a/crates/egui/src/widgets/plot/axis.rs b/crates/egui_plot/src/axis.rs similarity index 98% rename from crates/egui/src/widgets/plot/axis.rs rename to crates/egui_plot/src/axis.rs index 0ed2eabd9..93362270f 100644 --- a/crates/egui/src/widgets/plot/axis.rs +++ b/crates/egui_plot/src/axis.rs @@ -1,9 +1,7 @@ use std::{fmt::Debug, ops::RangeInclusive, sync::Arc}; -use epaint::{ - emath::{remap_clamp, round_to_decimals}, - Pos2, Rect, Shape, Stroke, TextShape, -}; +use egui::emath::{remap_clamp, round_to_decimals, Pos2, Rect}; +use egui::epaint::{Shape, Stroke, TextShape}; use crate::{Response, Sense, TextStyle, Ui, WidgetText}; diff --git a/crates/egui/src/widgets/plot/items/bar.rs b/crates/egui_plot/src/items/bar.rs similarity index 96% rename from crates/egui/src/widgets/plot/items/bar.rs rename to crates/egui_plot/src/items/bar.rs index 7c4dbab00..9e6124bd8 100644 --- a/crates/egui/src/widgets/plot/items/bar.rs +++ b/crates/egui_plot/src/items/bar.rs @@ -1,8 +1,8 @@ -use crate::emath::NumExt; -use crate::epaint::{Color32, RectShape, Rounding, Shape, Stroke}; +use egui::emath::NumExt; +use egui::epaint::{Color32, RectShape, Rounding, Shape, Stroke}; use super::{add_rulers_and_text, highlighted_color, Orientation, PlotConfig, RectElement}; -use crate::plot::{BarChart, Cursor, PlotPoint, PlotTransform}; +use crate::{BarChart, Cursor, PlotPoint, PlotTransform}; /// One bar in a [`BarChart`]. Potentially floating, allowing stacked bar charts. /// Width can be changed to allow variable-width histograms. @@ -185,6 +185,6 @@ impl RectElement for Bar { Orientation::Vertical => scale[1], }; let decimals = ((-scale.abs().log10()).ceil().at_least(0.0) as usize).at_most(6); - crate::plot::format_number(self.value, decimals) + crate::format_number(self.value, decimals) } } diff --git a/crates/egui/src/widgets/plot/items/box_elem.rs b/crates/egui_plot/src/items/box_elem.rs similarity index 98% rename from crates/egui/src/widgets/plot/items/box_elem.rs rename to crates/egui_plot/src/items/box_elem.rs index dab53fd77..1acf4c99b 100644 --- a/crates/egui/src/widgets/plot/items/box_elem.rs +++ b/crates/egui_plot/src/items/box_elem.rs @@ -1,8 +1,9 @@ -use crate::emath::NumExt; -use crate::epaint::{Color32, RectShape, Rounding, Shape, Stroke}; +use egui::emath::NumExt as _; +use egui::epaint::{Color32, RectShape, Rounding, Shape, Stroke}; + +use crate::{BoxPlot, Cursor, PlotPoint, PlotTransform}; use super::{add_rulers_and_text, highlighted_color, Orientation, PlotConfig, RectElement}; -use crate::plot::{BoxPlot, Cursor, PlotPoint, PlotTransform}; /// Contains the values of a single box in a box plot. #[derive(Clone, Debug, PartialEq)] diff --git a/crates/egui/src/widgets/plot/items/mod.rs b/crates/egui_plot/src/items/mod.rs similarity index 95% rename from crates/egui/src/widgets/plot/items/mod.rs rename to crates/egui_plot/src/items/mod.rs index 2cbcb26dd..e11a8947b 100644 --- a/crates/egui/src/widgets/plot/items/mod.rs +++ b/crates/egui_plot/src/items/mod.rs @@ -2,8 +2,7 @@ use std::ops::RangeInclusive; -use epaint::util::FloatOrd; -use epaint::Mesh; +use epaint::{emath::Rot2, util::FloatOrd, Mesh}; use crate::*; @@ -188,10 +187,10 @@ impl PlotItem for HLine { // Round to minimize aliasing: let points = vec![ - ui.ctx().round_pos_to_pixels( + ui.painter().round_pos_to_pixels( transform.position_from_point(&PlotPoint::new(transform.bounds().min[0], *y)), ), - ui.ctx().round_pos_to_pixels( + ui.painter().round_pos_to_pixels( transform.position_from_point(&PlotPoint::new(transform.bounds().max[0], *y)), ), ]; @@ -304,10 +303,10 @@ impl PlotItem for VLine { // Round to minimize aliasing: let points = vec![ - ui.ctx().round_pos_to_pixels( + ui.painter().round_pos_to_pixels( transform.position_from_point(&PlotPoint::new(*x, transform.bounds().min[1])), ), - ui.ctx().round_pos_to_pixels( + ui.painter().round_pos_to_pixels( transform.position_from_point(&PlotPoint::new(*x, transform.bounds().max[1])), ), ]; @@ -517,7 +516,7 @@ pub struct Polygon { pub(super) stroke: Stroke, pub(super) name: String, pub(super) highlight: bool, - pub(super) fill_alpha: f32, + pub(super) fill_color: Option, pub(super) style: LineStyle, } @@ -528,7 +527,7 @@ impl Polygon { stroke: Stroke::new(1.0, Color32::TRANSPARENT), name: Default::default(), highlight: false, - fill_alpha: DEFAULT_FILL_ALPHA, + fill_color: None, style: LineStyle::Solid, } } @@ -552,15 +551,21 @@ impl Polygon { self } - /// Stroke color. Default is `Color32::TRANSPARENT` which means a color will be auto-assigned. + #[deprecated = "Use `fill_color`."] + #[allow(unused, clippy::needless_pass_by_value)] pub fn color(mut self, color: impl Into) -> Self { - self.stroke.color = color.into(); self } - /// Alpha of the filled area. - pub fn fill_alpha(mut self, alpha: impl Into) -> Self { - self.fill_alpha = alpha.into(); + #[deprecated = "Use `fill_color`."] + #[allow(unused, clippy::needless_pass_by_value)] + pub fn fill_alpha(mut self, _alpha: impl Into) -> Self { + self + } + + /// Fill color. Defaults to the stroke color with added transparency. + pub fn fill_color(mut self, color: impl Into) -> Self { + self.fill_color = Some(color.into()); self } @@ -589,24 +594,20 @@ impl PlotItem for Polygon { series, stroke, highlight, - mut fill_alpha, + fill_color, style, .. } = self; - if *highlight { - fill_alpha = (2.0 * fill_alpha).at_most(1.0); - } - let mut values_tf: Vec<_> = series .points() .iter() .map(|v| transform.position_from_point(v)) .collect(); - let fill = Rgba::from(stroke.color).to_opaque().multiply(fill_alpha); + let fill_color = fill_color.unwrap_or(stroke.color.linear_multiply(DEFAULT_FILL_ALPHA)); - let shape = Shape::convex_polygon(values_tf.clone(), fill, Stroke::NONE); + let shape = Shape::convex_polygon(values_tf.clone(), fill_color, Stroke::NONE); shapes.push(shape); values_tf.push(*values_tf.first().unwrap()); style.style_line(values_tf, *stroke, *highlight, shapes); @@ -1135,11 +1136,11 @@ pub struct PlotImage { pub(super) texture_id: TextureId, pub(super) uv: Rect, pub(super) size: Vec2, + pub(crate) rotation: f64, pub(super) bg_fill: Color32, pub(super) tint: Color32, pub(super) highlight: bool, pub(super) name: String, - pub(crate) rotation: Option<(f32, Vec2)>, } impl PlotImage { @@ -1156,9 +1157,9 @@ impl PlotImage { texture_id: texture_id.into(), uv: Rect::from_min_max(pos2(0.0, 0.0), pos2(1.0, 1.0)), size: size.into(), + rotation: 0.0, bg_fill: Default::default(), tint: Color32::WHITE, - rotation: None, } } @@ -1198,14 +1199,9 @@ impl PlotImage { self } - /// Rotate the image about an origin by some angle - /// - /// Positive angle is clockwise. - /// Origin is a vector in normalized UV space ((0,0) in top-left, (1,1) bottom right). - /// - /// To rotate about the center you can pass `Vec2::splat(0.5)` as the origin. - pub fn rotate(mut self, angle: f32, origin: Vec2) -> Self { - self.rotation = Some((angle, origin)); + /// Rotate the image counter-clockwise around its center by an angle in radians. + pub fn rotate(mut self, angle: f64) -> Self { + self.rotation = angle; self } } @@ -1214,6 +1210,7 @@ impl PlotItem for PlotImage { fn shapes(&self, ui: &mut Ui, transform: &PlotTransform, shapes: &mut Vec) { let Self { position, + rotation, texture_id, uv, size, @@ -1222,31 +1219,40 @@ impl PlotItem for PlotImage { highlight, .. } = self; - let rect = { + let image_screen_rect = { let left_top = PlotPoint::new( - position.x as f32 - size.x / 2.0, - position.y as f32 - size.y / 2.0, + position.x - 0.5 * size.x as f64, + position.y - 0.5 * size.y as f64, ); let right_bottom = PlotPoint::new( - position.x as f32 + size.x / 2.0, - position.y as f32 + size.y / 2.0, + position.x + 0.5 * size.x as f64, + position.y + 0.5 * size.y as f64, ); - let left_top_tf = transform.position_from_point(&left_top); - let right_bottom_tf = transform.position_from_point(&right_bottom); - Rect::from_two_pos(left_top_tf, right_bottom_tf) + let left_top_screen = transform.position_from_point(&left_top); + let right_bottom_screen = transform.position_from_point(&right_bottom); + Rect::from_two_pos(left_top_screen, right_bottom_screen) }; - let mut image = Image::new(*texture_id, *size) + let screen_rotation = -*rotation as f32; + Image::new(*texture_id, image_screen_rect.size()) .bg_fill(*bg_fill) .tint(*tint) - .uv(*uv); - if let Some((angle, origin)) = self.rotation { - image = image.rotate(angle, origin); - } - image.paint_at(ui, rect); + .uv(*uv) + .rotate(screen_rotation, Vec2::splat(0.5)) + .paint_at(ui, image_screen_rect); if *highlight { - shapes.push(Shape::rect_stroke( - rect, - 0.0, + let center = image_screen_rect.center(); + let rotation = Rot2::from_angle(screen_rotation); + let outline = [ + image_screen_rect.right_bottom(), + image_screen_rect.right_top(), + image_screen_rect.left_top(), + image_screen_rect.left_bottom(), + ] + .iter() + .map(|point| center + rotation * (*point - center)) + .collect(); + shapes.push(Shape::closed_line( + outline, Stroke::new(1.0, ui.visuals().strong_text_color()), )); } diff --git a/crates/egui/src/widgets/plot/items/rect_elem.rs b/crates/egui_plot/src/items/rect_elem.rs similarity index 94% rename from crates/egui/src/widgets/plot/items/rect_elem.rs rename to crates/egui_plot/src/items/rect_elem.rs index 681d4fe8e..1ac470c77 100644 --- a/crates/egui/src/widgets/plot/items/rect_elem.rs +++ b/crates/egui_plot/src/items/rect_elem.rs @@ -1,7 +1,9 @@ +use egui::emath::NumExt as _; +use egui::epaint::{Color32, Rgba, Stroke}; + +use crate::transform::{PlotBounds, PlotTransform}; + use super::{Orientation, PlotPoint}; -use crate::plot::transform::{PlotBounds, PlotTransform}; -use epaint::emath::NumExt; -use epaint::{Color32, Rgba, Stroke}; /// Trait that abstracts from rectangular 'Value'-like elements, such as bars or boxes pub(super) trait RectElement { diff --git a/crates/egui/src/widgets/plot/items/values.rs b/crates/egui_plot/src/items/values.rs similarity index 99% rename from crates/egui/src/widgets/plot/items/values.rs rename to crates/egui_plot/src/items/values.rs index 52fceba7b..55e0e464b 100644 --- a/crates/egui/src/widgets/plot/items/values.rs +++ b/crates/egui_plot/src/items/values.rs @@ -1,7 +1,8 @@ -use epaint::{Pos2, Shape, Stroke, Vec2}; use std::ops::{Bound, RangeBounds, RangeInclusive}; -use crate::plot::transform::PlotBounds; +use egui::{Pos2, Shape, Stroke, Vec2}; + +use crate::transform::PlotBounds; /// A point coordinate in the plot. /// diff --git a/crates/egui/src/widgets/plot/legend.rs b/crates/egui_plot/src/legend.rs similarity index 100% rename from crates/egui/src/widgets/plot/legend.rs rename to crates/egui_plot/src/legend.rs diff --git a/crates/egui/src/widgets/plot/mod.rs b/crates/egui_plot/src/lib.rs similarity index 99% rename from crates/egui/src/widgets/plot/mod.rs rename to crates/egui_plot/src/lib.rs index e1f3ffc0b..08a6ebd15 100644 --- a/crates/egui/src/widgets/plot/mod.rs +++ b/crates/egui_plot/src/lib.rs @@ -1,8 +1,12 @@ //! Simple plotting library. +//! +//! ## Feature flags +#![cfg_attr(feature = "document-features", doc = document_features::document_features!())] +//! use std::{ops::RangeInclusive, sync::Arc}; -use ahash::HashMap; +use egui::ahash::HashMap; use epaint::util::FloatOrd; use epaint::Hsva; @@ -10,7 +14,7 @@ use axis::AxisWidget; use items::PlotItem; use legend::LegendWidget; -use crate::*; +use egui::*; pub use items::{ Arrows, Bar, BarChart, BoxElem, BoxPlot, BoxSpread, HLine, Line, LineStyle, MarkerShape, @@ -182,7 +186,7 @@ pub struct PlotResponse { /// /// ``` /// # egui::__run_test_ui(|ui| { -/// use egui::plot::{Line, Plot, PlotPoints}; +/// use egui_plot::{Line, Plot, PlotPoints}; /// let sin: PlotPoints = (0..1000).map(|i| { /// let x = i as f64 * 0.01; /// [x, x.sin()] @@ -394,7 +398,7 @@ impl Plot { /// /// ``` /// # egui::__run_test_ui(|ui| { - /// use egui::plot::{Line, Plot, PlotPoints}; + /// use egui_plot::{Line, Plot, PlotPoints}; /// let sin: PlotPoints = (0..1000).map(|i| { /// let x = i as f64 * 0.01; /// [x, x.sin()] @@ -443,7 +447,7 @@ impl Plot { /// For example, if x = 80..=230 is visible and you want big marks at steps of /// 100 and small ones at 25, you can return: /// ```no_run - /// # use egui::plot::GridMark; + /// # use egui_plot::GridMark; /// vec![ /// // 100s /// GridMark { value: 100.0, step_size: 100.0 }, @@ -1781,8 +1785,8 @@ impl PreparedPlot { if self.sharp_grid_lines { // Round to avoid aliasing - p0 = ui.ctx().round_pos_to_pixels(p0); - p1 = ui.ctx().round_pos_to_pixels(p1); + p0 = ui.painter().round_pos_to_pixels(p0); + p1 = ui.painter().round_pos_to_pixels(p1); } shapes.push(( @@ -1851,7 +1855,7 @@ impl PreparedPlot { /// Returns next bigger power in given base /// e.g. /// ```ignore -/// use egui::plot::next_power; +/// use egui_plot::next_power; /// assert_eq!(next_power(0.01, 10.0), 0.01); /// assert_eq!(next_power(0.02, 10.0), 0.1); /// assert_eq!(next_power(0.2, 10.0), 1); diff --git a/crates/egui/src/widgets/plot/memory.rs b/crates/egui_plot/src/memory.rs similarity index 100% rename from crates/egui/src/widgets/plot/memory.rs rename to crates/egui_plot/src/memory.rs diff --git a/crates/egui/src/widgets/plot/transform.rs b/crates/egui_plot/src/transform.rs similarity index 100% rename from crates/egui/src/widgets/plot/transform.rs rename to crates/egui_plot/src/transform.rs diff --git a/crates/emath/Cargo.toml b/crates/emath/Cargo.toml index 8fea677e3..70141c56d 100644 --- a/crates/emath/Cargo.toml +++ b/crates/emath/Cargo.toml @@ -24,6 +24,7 @@ default = [] ## Enable additional checks if debug assertions are enabled (debug builds). extra_debug_asserts = [] + ## Always enable additional checks. extra_asserts = [] diff --git a/crates/epaint/src/textures.rs b/crates/epaint/src/textures.rs index abe8d0b91..b27976f52 100644 --- a/crates/epaint/src/textures.rs +++ b/crates/epaint/src/textures.rs @@ -215,7 +215,7 @@ impl TexturesDelta { } pub fn append(&mut self, mut newer: TexturesDelta) { - self.set.extend(newer.set.into_iter()); + self.set.extend(newer.set); self.free.append(&mut newer.free); } diff --git a/deny.toml b/deny.toml index 3c1f21d8c..fa0280809 100644 --- a/deny.toml +++ b/deny.toml @@ -22,6 +22,7 @@ unmaintained = "warn" yanked = "deny" ignore = [ "RUSTSEC-2020-0071", # https://rustsec.org/advisories/RUSTSEC-2020-0071 - chrono/time: Potential segfault in the time crate + "RUSTSEC-2023-0052", # https://rustsec.org/advisories/RUSTSEC-2023-0052 - can be fixed by `cargo update -p ureq`, but then we run into duplicate crates: https://github.com/algesten/ureq/issues/653 ] [bans] diff --git a/examples/file_dialog/src/main.rs b/examples/file_dialog/src/main.rs index ba82e3de0..937cd083b 100644 --- a/examples/file_dialog/src/main.rs +++ b/examples/file_dialog/src/main.rs @@ -62,10 +62,18 @@ impl eframe::App for MyApp { } else { "???".to_owned() }; - if let Some(bytes) = &file.bytes { - use std::fmt::Write as _; - write!(info, " ({} bytes)", bytes.len()).ok(); + + let mut additional_info = vec![]; + if !file.mime.is_empty() { + additional_info.push(format!("type: {}", file.mime)); } + if let Some(bytes) = &file.bytes { + additional_info.push(format!("{} bytes", bytes.len())); + } + if !additional_info.is_empty() { + info += &format!(" ({})", additional_info.join(", ")); + } + ui.label(info); } }); diff --git a/examples/save_plot/Cargo.toml b/examples/save_plot/Cargo.toml index 2a12692f8..e2e2e83ff 100644 --- a/examples/save_plot/Cargo.toml +++ b/examples/save_plot/Cargo.toml @@ -11,6 +11,7 @@ publish = false eframe = { path = "../../crates/eframe", features = [ "__screenshot", # __screenshot is so we can dump a screenshot using EFRAME_SCREENSHOT_TO ] } +egui_plot = { path = "../../crates/egui_plot" } image = { version = "0.24", default-features = false, features = ["png"] } rfd = "0.11.0" env_logger = "0.10" diff --git a/examples/save_plot/src/main.rs b/examples/save_plot/src/main.rs index fc6a3e9e7..cabe2c836 100644 --- a/examples/save_plot/src/main.rs +++ b/examples/save_plot/src/main.rs @@ -1,8 +1,8 @@ #![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] // hide console window on Windows in release -use eframe::egui::plot::{Legend, Line, Plot, PlotPoints}; use eframe::egui::ColorImage; use eframe::egui::{self, ViewportRender}; +use egui_plot::{Legend, Line, Plot, PlotPoints}; fn main() -> Result<(), eframe::Error> { env_logger::init(); // Log to stderr (if you run with `RUST_LOG=debug`). diff --git a/scripts/generate_changelog.py b/scripts/generate_changelog.py index 2e97d01b2..aed02dd07 100755 --- a/scripts/generate_changelog.py +++ b/scripts/generate_changelog.py @@ -131,6 +131,7 @@ def main() -> None: "ecolor", "eframe", "egui_extras", + "egui_plot", "egui_glow", "egui-wgpu", "egui-winit", @@ -141,8 +142,6 @@ def main() -> None: unsorted_prs = [] unsorted_commits = [] - plot = [] - for commit_info, pr_info in zip(commit_infos, pr_infos): hexsha = commit_info.hexsha title = commit_info.title @@ -170,14 +169,11 @@ def main() -> None: continue # We get so many typo PRs. Let's not flood the changelog with them. added = False - if 'plot' in labels: - plot.append(summary) - added = True - else: - for crate in crate_names: - if crate in labels: - sections.setdefault(crate, []).append(summary) - added = True + + for crate in crate_names: + if crate in labels: + sections.setdefault(crate, []).append(summary) + added = True if not added: if not any(label in labels for label in ignore_labels): @@ -188,9 +184,6 @@ def main() -> None: if crate in sections: summary = sections[crate] print_section(crate, summary) - if crate == 'egui': - if 0 < len(plot): - print_section("egui plot", plot) print_section("Unsorted PRs", unsorted_prs) print_section("Unsorted commits", unsorted_commits)