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/).
* [`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-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_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).

View file

@ -270,6 +270,10 @@ pub struct NativeOptions {
/// You should avoid having a [`egui::CentralPanel`], or make sure its frame is also transparent.
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.
///
/// The default is `true`.
@ -389,6 +393,7 @@ impl Default for NativeOptions {
max_window_size: None,
resizable: true,
transparent: false,
mouse_passthrough: false,
vsync: true,
multisampling: 0,
depth_buffer: 0,

View file

@ -383,6 +383,9 @@ mod glow_integration {
integration.egui_ctx.set_visuals(theme.egui_visuals());
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();

View file

@ -244,6 +244,13 @@ impl Prepared {
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 {
let paint_rect = self.paint_rect();
@ -258,6 +265,6 @@ impl Prepared {
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
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
#[derive(Clone)]
struct AutoBounds {
#[derive(Copy, Clone)]
struct AxisBools {
x: bool,
y: bool,
}
impl AutoBounds {
fn from_bool(val: bool) -> Self {
AutoBounds { x: val, y: val }
}
fn any(&self) -> bool {
self.x || self.y
}
}
impl From<bool> for AutoBounds {
impl From<bool> for AxisBools {
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))]
#[derive(Clone)]
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>,
hidden_items: ahash::HashSet<String>,
min_auto_bounds: PlotBounds,
last_screen_transform: ScreenTransform,
/// Allows to remember the first click position when performing a boxed zoom
last_click_pos_for_zoom: Option<Pos2>,
@ -268,6 +259,7 @@ pub struct Plot {
allow_zoom: bool,
allow_drag: bool,
allow_scroll: bool,
auto_bounds: AxisBools,
min_auto_bounds: PlotBounds,
margin_fraction: Vec2,
allow_boxed_zoom: bool,
@ -281,6 +273,8 @@ pub struct Plot {
data_aspect: Option<f32>,
view_aspect: Option<f32>,
reset: bool,
show_x: bool,
show_y: bool,
label_formatter: LabelFormatter,
@ -303,6 +297,7 @@ impl Plot {
allow_zoom: true,
allow_drag: true,
allow_scroll: true,
auto_bounds: false.into(),
min_auto_bounds: PlotBounds::NOTHING,
margin_fraction: Vec2::splat(0.05),
allow_boxed_zoom: true,
@ -316,6 +311,8 @@ impl Plot {
data_aspect: None,
view_aspect: None,
reset: false,
show_x: true,
show_y: true,
label_formatter: None,
@ -402,7 +399,7 @@ impl Plot {
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.
pub fn set_margin_fraction(mut self, margin_fraction: Vec2) -> Self {
@ -556,6 +553,18 @@ impl Plot {
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.
pub fn legend(mut self, legend: Legend) -> Self {
self.legend_config = Some(legend);
@ -592,6 +601,12 @@ impl Plot {
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.
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))
@ -611,6 +626,7 @@ impl Plot {
allow_drag,
allow_boxed_zoom,
boxed_zoom_pointer_button: boxed_zoom_pointer,
auto_bounds,
min_auto_bounds,
margin_fraction,
width,
@ -624,6 +640,7 @@ impl Plot {
coordinates_formatter,
axis_formatters,
legend_config,
reset,
show_background,
show_axes,
linked_axes,
@ -661,11 +678,19 @@ impl Plot {
// Load or initialize the memory.
let plot_id = ui.make_persistent_id(id_source);
ui.ctx().check_for_id_clash(plot_id, rect, "Plot");
let mut memory = PlotMemory::load(ui.ctx(), plot_id).unwrap_or_else(|| PlotMemory {
auto_bounds: (!min_auto_bounds.is_valid()).into(),
let memory = if reset {
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,
hidden_items: Default::default(),
min_auto_bounds,
last_screen_transform: ScreenTransform::new(
rect,
min_auto_bounds,
@ -675,24 +700,12 @@ impl Plot {
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 {
mut auto_bounds,
mut bounds_modified,
mut hovered_entry,
mut hidden_items,
last_screen_transform,
mut last_click_pos_for_zoom,
..
} = memory;
// Call the plot build function.
@ -774,52 +787,51 @@ impl Plot {
if let Some(linked_bounds) = axes.get() {
if axes.link_x {
bounds.set_x(&linked_bounds);
// Turn off auto bounds to keep it from overriding what we just set.
auto_bounds.x = false;
// Mark the axis as modified to prevent it from being changed.
bounds_modified.x = true;
}
if axes.link_y {
bounds.set_y(&linked_bounds);
// Turn off auto bounds to keep it from overriding what we just set.
auto_bounds.y = false;
// Mark the axis as modified to prevent it from being changed.
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) {
auto_bounds = true.into();
bounds_modified = false.into();
}
if !bounds.is_valid() {
auto_bounds = true.into();
// Reset bounds to initial bounds if we haven't been modified.
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.
if auto_bounds.any() {
if auto_bounds.x {
bounds.set_x(&min_auto_bounds);
}
if auto_bounds.y {
bounds.set_y(&min_auto_bounds);
}
if auto_x || auto_y {
for item in &items {
let item_bounds = item.bounds();
if auto_bounds.x {
if auto_x {
bounds.merge_x(&item_bounds);
}
if auto_bounds.y {
if auto_y {
bounds.merge_y(&item_bounds);
}
}
if auto_bounds.x {
if auto_x {
bounds.add_relative_margin_x(margin_fraction);
}
if auto_bounds.y {
if auto_y {
bounds.add_relative_margin_y(margin_fraction);
}
}
@ -842,7 +854,7 @@ impl Plot {
if allow_drag && response.dragged_by(PointerButton::Primary) {
response = response.on_hover_cursor(CursorIcon::Grabbing);
transform.translate_bounds(-response.drag_delta());
auto_bounds = false.into();
bounds_modified = true.into();
}
// Zooming
@ -889,7 +901,7 @@ impl Plot {
};
if new_bounds.is_valid() {
transform.set_bounds(new_bounds);
auto_bounds = false.into();
bounds_modified = true.into();
}
// reset the boxed zoom state
last_click_pos_for_zoom = None;
@ -906,14 +918,14 @@ impl Plot {
};
if zoom_factor != Vec2::splat(1.0) {
transform.zoom(zoom_factor, hover_pos);
auto_bounds = false.into();
bounds_modified = true.into();
}
}
if allow_scroll {
let scroll_delta = ui.input().scroll_delta;
if scroll_delta != Vec2::ZERO {
transform.translate_bounds(-scroll_delta);
auto_bounds = false.into();
bounds_modified = true.into();
}
}
}
@ -963,10 +975,9 @@ impl Plot {
}
let memory = PlotMemory {
auto_bounds,
bounds_modified,
hovered_entry,
hidden_items,
min_auto_bounds,
last_screen_transform: transform,
last_click_pos_for_zoom,
};

View file

@ -40,10 +40,26 @@ impl PlotBounds {
&& 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 {
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 {
self.max[0] - self.min[0]
}
@ -181,8 +197,11 @@ pub(crate) struct ScreenTransform {
impl ScreenTransform {
pub fn new(frame: Rect, mut bounds: PlotBounds, x_centered: bool, y_centered: bool) -> Self {
// Make sure they are not empty.
if !bounds.is_valid() {
bounds = PlotBounds::new_symmetrical(1.0);
if !bounds.is_valid_x() {
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.

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};
@ -10,6 +10,7 @@ use crate::{Response, Sense, Ui, Widget};
pub struct Spinner {
/// Uses the style's `interact_size` if `None`.
size: Option<f32>,
color: Option<Color32>,
}
impl Spinner {
@ -24,6 +25,12 @@ impl Spinner {
self.size = Some(size);
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 {
@ -31,6 +38,9 @@ impl Widget for Spinner {
let size = self
.size
.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());
if ui.is_rect_visible(rect) {
@ -47,10 +57,8 @@ impl Widget for Spinner {
rect.center() + radius * vec2(cos as f32, sin as f32)
})
.collect();
ui.painter().add(Shape::line(
points,
Stroke::new(3.0, ui.visuals().strong_text_color()),
));
ui.painter()
.add(Shape::line(points, Stroke::new(3.0, color)));
}
response