Merge branch 'master' into data_aspect

This commit is contained in:
Emil Ernerfeldt 2022-10-02 08:46:54 +02:00
commit e3f3410e1a
7 changed files with 122 additions and 68 deletions

View file

@ -175,6 +175,7 @@ These are the official egui integrations:
* [`amethyst_egui`](https://github.com/jgraef/amethyst_egui) for [the Amethyst game engine](https://amethyst.rs/). * [`amethyst_egui`](https://github.com/jgraef/amethyst_egui) for [the Amethyst game engine](https://amethyst.rs/).
* [`bevy_egui`](https://github.com/mvlabat/bevy_egui) for [the Bevy game engine](https://bevyengine.org/). * [`bevy_egui`](https://github.com/mvlabat/bevy_egui) for [the Bevy game engine](https://bevyengine.org/).
* [`egui_glfw_gl`](https://github.com/cohaereo/egui_glfw_gl) for [GLFW](https://crates.io/crates/glfw). * [`egui_glfw_gl`](https://github.com/cohaereo/egui_glfw_gl) for [GLFW](https://crates.io/crates/glfw).
* [`egui-glutin-gl`](https://github.com/h3r2tic/egui-glutin-gl/) for [glutin](https://crates.io/crates/glutin).
* [`egui_sdl2_gl`](https://crates.io/crates/egui_sdl2_gl) for [SDL2](https://crates.io/crates/sdl2). * [`egui_sdl2_gl`](https://crates.io/crates/egui_sdl2_gl) for [SDL2](https://crates.io/crates/sdl2).
* [`egui_sdl2_platform`](https://github.com/ComLarsic/egui_sdl2_platform) for [SDL2](https://crates.io/crates/sdl2). * [`egui_sdl2_platform`](https://github.com/ComLarsic/egui_sdl2_platform) for [SDL2](https://crates.io/crates/sdl2).
* [`egui_vulkano`](https://github.com/derivator/egui_vulkano) for [Vulkano](https://github.com/vulkano-rs/vulkano). * [`egui_vulkano`](https://github.com/derivator/egui_vulkano) for [Vulkano](https://github.com/vulkano-rs/vulkano).

View file

@ -270,6 +270,10 @@ pub struct NativeOptions {
/// You should avoid having a [`egui::CentralPanel`], or make sure its frame is also transparent. /// You should avoid having a [`egui::CentralPanel`], or make sure its frame is also transparent.
pub transparent: bool, pub transparent: bool,
/// On desktop: mouse clicks pass through the window, used for non-interactable overlays
/// Generally you would use this in conjunction with always_on_top
pub mouse_passthrough: bool,
/// Turn on vertical syncing, limiting the FPS to the display refresh rate. /// Turn on vertical syncing, limiting the FPS to the display refresh rate.
/// ///
/// The default is `true`. /// The default is `true`.
@ -389,6 +393,7 @@ impl Default for NativeOptions {
max_window_size: None, max_window_size: None,
resizable: true, resizable: true,
transparent: false, transparent: false,
mouse_passthrough: false,
vsync: true, vsync: true,
multisampling: 0, multisampling: 0,
depth_buffer: 0, depth_buffer: 0,

View file

@ -383,6 +383,9 @@ mod glow_integration {
integration.egui_ctx.set_visuals(theme.egui_visuals()); integration.egui_ctx.set_visuals(theme.egui_visuals());
gl_window.window().set_ime_allowed(true); gl_window.window().set_ime_allowed(true);
if self.native_options.mouse_passthrough {
gl_window.window().set_cursor_hittest(false).unwrap();
}
{ {
let event_loop_proxy = self.repaint_proxy.clone(); let event_loop_proxy = self.repaint_proxy.clone();

View file

@ -244,6 +244,13 @@ impl Prepared {
rect rect
} }
fn content_with_margin(&self) -> Rect {
let mut rect = self.content_ui.min_rect();
rect.min -= self.frame.inner_margin.left_top() + self.frame.outer_margin.left_top();
rect.max += self.frame.inner_margin.right_bottom() + self.frame.outer_margin.right_bottom();
rect
}
pub fn end(self, ui: &mut Ui) -> Response { pub fn end(self, ui: &mut Ui) -> Response {
let paint_rect = self.paint_rect(); let paint_rect = self.paint_rect();
@ -258,6 +265,6 @@ impl Prepared {
ui.painter().set(where_to_put_background, shape); ui.painter().set(where_to_put_background, shape);
} }
ui.allocate_rect(paint_rect, Sense::hover()) ui.allocate_rect(self.content_with_margin(), Sense::hover())
} }
} }

View file

@ -73,25 +73,15 @@ impl Default for CoordinatesFormatter {
const MIN_LINE_SPACING_IN_POINTS: f64 = 6.0; // TODO(emilk): large enough for a wide label const MIN_LINE_SPACING_IN_POINTS: f64 = 6.0; // TODO(emilk): large enough for a wide label
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
#[derive(Clone)] #[derive(Copy, Clone)]
struct AutoBounds { struct AxisBools {
x: bool, x: bool,
y: bool, y: bool,
} }
impl AutoBounds { impl From<bool> for AxisBools {
fn from_bool(val: bool) -> Self {
AutoBounds { x: val, y: val }
}
fn any(&self) -> bool {
self.x || self.y
}
}
impl From<bool> for AutoBounds {
fn from(val: bool) -> Self { fn from(val: bool) -> Self {
AutoBounds::from_bool(val) AxisBools { x: val, y: val }
} }
} }
@ -99,10 +89,11 @@ impl From<bool> for AutoBounds {
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
#[derive(Clone)] #[derive(Clone)]
struct PlotMemory { struct PlotMemory {
auto_bounds: AutoBounds, /// Indicates if the user has modified the bounds, for example by moving or zooming,
/// or if the bounds should be calculated based by included point or auto bounds.
bounds_modified: AxisBools,
hovered_entry: Option<String>, hovered_entry: Option<String>,
hidden_items: ahash::HashSet<String>, hidden_items: ahash::HashSet<String>,
min_auto_bounds: PlotBounds,
last_screen_transform: ScreenTransform, last_screen_transform: ScreenTransform,
/// Allows to remember the first click position when performing a boxed zoom /// Allows to remember the first click position when performing a boxed zoom
last_click_pos_for_zoom: Option<Pos2>, last_click_pos_for_zoom: Option<Pos2>,
@ -268,6 +259,7 @@ pub struct Plot {
allow_zoom: bool, allow_zoom: bool,
allow_drag: bool, allow_drag: bool,
allow_scroll: bool, allow_scroll: bool,
auto_bounds: AxisBools,
min_auto_bounds: PlotBounds, min_auto_bounds: PlotBounds,
margin_fraction: Vec2, margin_fraction: Vec2,
allow_boxed_zoom: bool, allow_boxed_zoom: bool,
@ -281,6 +273,8 @@ pub struct Plot {
data_aspect: Option<f32>, data_aspect: Option<f32>,
view_aspect: Option<f32>, view_aspect: Option<f32>,
reset: bool,
show_x: bool, show_x: bool,
show_y: bool, show_y: bool,
label_formatter: LabelFormatter, label_formatter: LabelFormatter,
@ -303,6 +297,7 @@ impl Plot {
allow_zoom: true, allow_zoom: true,
allow_drag: true, allow_drag: true,
allow_scroll: true, allow_scroll: true,
auto_bounds: false.into(),
min_auto_bounds: PlotBounds::NOTHING, min_auto_bounds: PlotBounds::NOTHING,
margin_fraction: Vec2::splat(0.05), margin_fraction: Vec2::splat(0.05),
allow_boxed_zoom: true, allow_boxed_zoom: true,
@ -316,6 +311,8 @@ impl Plot {
data_aspect: None, data_aspect: None,
view_aspect: None, view_aspect: None,
reset: false,
show_x: true, show_x: true,
show_y: true, show_y: true,
label_formatter: None, label_formatter: None,
@ -402,7 +399,7 @@ impl Plot {
self self
} }
/// Set the side margin as a fraction of the plot size. /// Set the side margin as a fraction of the plot size. Only used for auto bounds.
/// ///
/// For instance, a value of `0.1` will add 10% space on both sides. /// For instance, a value of `0.1` will add 10% space on both sides.
pub fn set_margin_fraction(mut self, margin_fraction: Vec2) -> Self { pub fn set_margin_fraction(mut self, margin_fraction: Vec2) -> Self {
@ -556,6 +553,18 @@ impl Plot {
self self
} }
/// Expand bounds to fit all items across the x axis, including values given by `include_x`.
pub fn auto_bounds_x(mut self) -> Self {
self.auto_bounds.x = true;
self
}
/// Expand bounds to fit all items across the y axis, including values given by `include_y`.
pub fn auto_bounds_y(mut self) -> Self {
self.auto_bounds.y = true;
self
}
/// Show a legend including all named items. /// Show a legend including all named items.
pub fn legend(mut self, legend: Legend) -> Self { pub fn legend(mut self, legend: Legend) -> Self {
self.legend_config = Some(legend); self.legend_config = Some(legend);
@ -592,6 +601,12 @@ impl Plot {
self self
} }
/// Resets the plot.
pub fn reset(mut self) -> Self {
self.reset = true;
self
}
/// Interact with and add items to the plot and finally draw it. /// Interact with and add items to the plot and finally draw it.
pub fn show<R>(self, ui: &mut Ui, build_fn: impl FnOnce(&mut PlotUi) -> R) -> InnerResponse<R> { pub fn show<R>(self, ui: &mut Ui, build_fn: impl FnOnce(&mut PlotUi) -> R) -> InnerResponse<R> {
self.show_dyn(ui, Box::new(build_fn)) self.show_dyn(ui, Box::new(build_fn))
@ -611,6 +626,7 @@ impl Plot {
allow_drag, allow_drag,
allow_boxed_zoom, allow_boxed_zoom,
boxed_zoom_pointer_button: boxed_zoom_pointer, boxed_zoom_pointer_button: boxed_zoom_pointer,
auto_bounds,
min_auto_bounds, min_auto_bounds,
margin_fraction, margin_fraction,
width, width,
@ -624,6 +640,7 @@ impl Plot {
coordinates_formatter, coordinates_formatter,
axis_formatters, axis_formatters,
legend_config, legend_config,
reset,
show_background, show_background,
show_axes, show_axes,
linked_axes, linked_axes,
@ -661,11 +678,19 @@ impl Plot {
// Load or initialize the memory. // Load or initialize the memory.
let plot_id = ui.make_persistent_id(id_source); let plot_id = ui.make_persistent_id(id_source);
ui.ctx().check_for_id_clash(plot_id, rect, "Plot"); ui.ctx().check_for_id_clash(plot_id, rect, "Plot");
let mut memory = PlotMemory::load(ui.ctx(), plot_id).unwrap_or_else(|| PlotMemory { let memory = if reset {
auto_bounds: (!min_auto_bounds.is_valid()).into(), if let Some(axes) = linked_axes.as_ref() {
axes.bounds.set(None);
};
None
} else {
PlotMemory::load(ui.ctx(), plot_id)
}
.unwrap_or_else(|| PlotMemory {
bounds_modified: false.into(),
hovered_entry: None, hovered_entry: None,
hidden_items: Default::default(), hidden_items: Default::default(),
min_auto_bounds,
last_screen_transform: ScreenTransform::new( last_screen_transform: ScreenTransform::new(
rect, rect,
min_auto_bounds, min_auto_bounds,
@ -675,24 +700,12 @@ impl Plot {
last_click_pos_for_zoom: None, last_click_pos_for_zoom: None,
}); });
// If the min bounds changed, recalculate everything.
if min_auto_bounds != memory.min_auto_bounds {
memory = PlotMemory {
auto_bounds: (!min_auto_bounds.is_valid()).into(),
hovered_entry: None,
min_auto_bounds,
..memory
};
memory.clone().store(ui.ctx(), plot_id);
}
let PlotMemory { let PlotMemory {
mut auto_bounds, mut bounds_modified,
mut hovered_entry, mut hovered_entry,
mut hidden_items, mut hidden_items,
last_screen_transform, last_screen_transform,
mut last_click_pos_for_zoom, mut last_click_pos_for_zoom,
..
} = memory; } = memory;
// Call the plot build function. // Call the plot build function.
@ -774,52 +787,51 @@ impl Plot {
if let Some(linked_bounds) = axes.get() { if let Some(linked_bounds) = axes.get() {
if axes.link_x { if axes.link_x {
bounds.set_x(&linked_bounds); bounds.set_x(&linked_bounds);
// Turn off auto bounds to keep it from overriding what we just set. // Mark the axis as modified to prevent it from being changed.
auto_bounds.x = false; bounds_modified.x = true;
} }
if axes.link_y { if axes.link_y {
bounds.set_y(&linked_bounds); bounds.set_y(&linked_bounds);
// Turn off auto bounds to keep it from overriding what we just set. // Mark the axis as modified to prevent it from being changed.
auto_bounds.y = false; bounds_modified.y = true;
} }
} }
}; };
// Allow double clicking to reset to automatic bounds. // Allow double clicking to reset to the initial bounds.
if response.double_clicked_by(PointerButton::Primary) { if response.double_clicked_by(PointerButton::Primary) {
auto_bounds = true.into(); bounds_modified = false.into();
} }
if !bounds.is_valid() { // Reset bounds to initial bounds if we haven't been modified.
auto_bounds = true.into(); if !bounds_modified.x {
bounds.set_x(&min_auto_bounds);
} }
if !bounds_modified.y {
bounds.set_y(&min_auto_bounds);
}
let auto_x = !bounds_modified.x && (!min_auto_bounds.is_valid_x() || auto_bounds.x);
let auto_y = !bounds_modified.y && (!min_auto_bounds.is_valid_y() || auto_bounds.y);
// Set bounds automatically based on content. // Set bounds automatically based on content.
if auto_bounds.any() { if auto_x || auto_y {
if auto_bounds.x {
bounds.set_x(&min_auto_bounds);
}
if auto_bounds.y {
bounds.set_y(&min_auto_bounds);
}
for item in &items { for item in &items {
let item_bounds = item.bounds(); let item_bounds = item.bounds();
if auto_bounds.x { if auto_x {
bounds.merge_x(&item_bounds); bounds.merge_x(&item_bounds);
} }
if auto_bounds.y { if auto_y {
bounds.merge_y(&item_bounds); bounds.merge_y(&item_bounds);
} }
} }
if auto_bounds.x { if auto_x {
bounds.add_relative_margin_x(margin_fraction); bounds.add_relative_margin_x(margin_fraction);
} }
if auto_bounds.y { if auto_y {
bounds.add_relative_margin_y(margin_fraction); bounds.add_relative_margin_y(margin_fraction);
} }
} }
@ -842,7 +854,7 @@ impl Plot {
if allow_drag && response.dragged_by(PointerButton::Primary) { if allow_drag && response.dragged_by(PointerButton::Primary) {
response = response.on_hover_cursor(CursorIcon::Grabbing); response = response.on_hover_cursor(CursorIcon::Grabbing);
transform.translate_bounds(-response.drag_delta()); transform.translate_bounds(-response.drag_delta());
auto_bounds = false.into(); bounds_modified = true.into();
} }
// Zooming // Zooming
@ -889,7 +901,7 @@ impl Plot {
}; };
if new_bounds.is_valid() { if new_bounds.is_valid() {
transform.set_bounds(new_bounds); transform.set_bounds(new_bounds);
auto_bounds = false.into(); bounds_modified = true.into();
} }
// reset the boxed zoom state // reset the boxed zoom state
last_click_pos_for_zoom = None; last_click_pos_for_zoom = None;
@ -906,14 +918,14 @@ impl Plot {
}; };
if zoom_factor != Vec2::splat(1.0) { if zoom_factor != Vec2::splat(1.0) {
transform.zoom(zoom_factor, hover_pos); transform.zoom(zoom_factor, hover_pos);
auto_bounds = false.into(); bounds_modified = true.into();
} }
} }
if allow_scroll { if allow_scroll {
let scroll_delta = ui.input().scroll_delta; let scroll_delta = ui.input().scroll_delta;
if scroll_delta != Vec2::ZERO { if scroll_delta != Vec2::ZERO {
transform.translate_bounds(-scroll_delta); transform.translate_bounds(-scroll_delta);
auto_bounds = false.into(); bounds_modified = true.into();
} }
} }
} }
@ -963,10 +975,9 @@ impl Plot {
} }
let memory = PlotMemory { let memory = PlotMemory {
auto_bounds, bounds_modified,
hovered_entry, hovered_entry,
hidden_items, hidden_items,
min_auto_bounds,
last_screen_transform: transform, last_screen_transform: transform,
last_click_pos_for_zoom, last_click_pos_for_zoom,
}; };

View file

@ -40,10 +40,26 @@ impl PlotBounds {
&& self.max[1].is_finite() && self.max[1].is_finite()
} }
pub fn is_finite_x(&self) -> bool {
self.min[0].is_finite() && self.max[0].is_finite()
}
pub fn is_finite_y(&self) -> bool {
self.min[1].is_finite() && self.max[1].is_finite()
}
pub fn is_valid(&self) -> bool { pub fn is_valid(&self) -> bool {
self.is_finite() && self.width() > 0.0 && self.height() > 0.0 self.is_finite() && self.width() > 0.0 && self.height() > 0.0
} }
pub fn is_valid_x(&self) -> bool {
self.is_finite_x() && self.width() > 0.0
}
pub fn is_valid_y(&self) -> bool {
self.is_finite_y() && self.height() > 0.0
}
pub fn width(&self) -> f64 { pub fn width(&self) -> f64 {
self.max[0] - self.min[0] self.max[0] - self.min[0]
} }
@ -181,8 +197,11 @@ pub(crate) struct ScreenTransform {
impl ScreenTransform { impl ScreenTransform {
pub fn new(frame: Rect, mut bounds: PlotBounds, x_centered: bool, y_centered: bool) -> Self { pub fn new(frame: Rect, mut bounds: PlotBounds, x_centered: bool, y_centered: bool) -> Self {
// Make sure they are not empty. // Make sure they are not empty.
if !bounds.is_valid() { if !bounds.is_valid_x() {
bounds = PlotBounds::new_symmetrical(1.0); bounds.set_x(&PlotBounds::new_symmetrical(1.0));
}
if !bounds.is_valid_y() {
bounds.set_y(&PlotBounds::new_symmetrical(1.0));
} }
// Scale axes so that the origin is in the center. // Scale axes so that the origin is in the center.

View file

@ -1,4 +1,4 @@
use epaint::{emath::lerp, vec2, Pos2, Shape, Stroke}; use epaint::{emath::lerp, vec2, Color32, Pos2, Shape, Stroke};
use crate::{Response, Sense, Ui, Widget}; use crate::{Response, Sense, Ui, Widget};
@ -10,6 +10,7 @@ use crate::{Response, Sense, Ui, Widget};
pub struct Spinner { pub struct Spinner {
/// Uses the style's `interact_size` if `None`. /// Uses the style's `interact_size` if `None`.
size: Option<f32>, size: Option<f32>,
color: Option<Color32>,
} }
impl Spinner { impl Spinner {
@ -24,6 +25,12 @@ impl Spinner {
self.size = Some(size); self.size = Some(size);
self self
} }
/// Sets the spinner's color.
pub fn color(mut self, color: impl Into<Color32>) -> Self {
self.color = Some(color.into());
self
}
} }
impl Widget for Spinner { impl Widget for Spinner {
@ -31,6 +38,9 @@ impl Widget for Spinner {
let size = self let size = self
.size .size
.unwrap_or_else(|| ui.style().spacing.interact_size.y); .unwrap_or_else(|| ui.style().spacing.interact_size.y);
let color = self
.color
.unwrap_or_else(|| ui.visuals().strong_text_color());
let (rect, response) = ui.allocate_exact_size(vec2(size, size), Sense::hover()); let (rect, response) = ui.allocate_exact_size(vec2(size, size), Sense::hover());
if ui.is_rect_visible(rect) { if ui.is_rect_visible(rect) {
@ -47,10 +57,8 @@ impl Widget for Spinner {
rect.center() + radius * vec2(cos as f32, sin as f32) rect.center() + radius * vec2(cos as f32, sin as f32)
}) })
.collect(); .collect();
ui.painter().add(Shape::line( ui.painter()
points, .add(Shape::line(points, Stroke::new(3.0, color)));
Stroke::new(3.0, ui.visuals().strong_text_color()),
));
} }
response response