mirror of
https://github.com/emilk/egui.git
synced 2026-09-02 06:40:06 -04:00
Merge branch 'master' of https://github.com/emilk/egui into multiples_viewports
This commit is contained in:
@@ -55,6 +55,8 @@ impl State {
|
||||
/// });
|
||||
/// # });
|
||||
/// ```
|
||||
///
|
||||
/// The previous rectangle used by this area can be obtained through [`crate::Memory::area_rect()`].
|
||||
#[must_use = "You should call .show()"]
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct Area {
|
||||
|
||||
@@ -555,13 +555,12 @@ impl CollapsingHeader {
|
||||
let visuals = ui.style().interact_selectable(&header_response, selected);
|
||||
|
||||
if ui.visuals().collapsing_header_frame || show_background {
|
||||
ui.painter().add(epaint::RectShape {
|
||||
rect: header_response.rect.expand(visuals.expansion),
|
||||
rounding: visuals.rounding,
|
||||
fill: visuals.weak_bg_fill,
|
||||
stroke: visuals.bg_stroke,
|
||||
// stroke: Default::default(),
|
||||
});
|
||||
ui.painter().add(epaint::RectShape::new(
|
||||
header_response.rect.expand(visuals.expansion),
|
||||
visuals.rounding,
|
||||
visuals.weak_bg_fill,
|
||||
visuals.bg_stroke,
|
||||
));
|
||||
}
|
||||
|
||||
if selected || selectable && (header_response.hovered() || header_response.has_focus())
|
||||
|
||||
@@ -383,12 +383,12 @@ fn button_frame(
|
||||
|
||||
ui.painter().set(
|
||||
where_to_put_background,
|
||||
epaint::RectShape {
|
||||
rect: outer_rect.expand(visuals.expansion),
|
||||
rounding: visuals.rounding,
|
||||
fill: visuals.weak_bg_fill,
|
||||
stroke: visuals.bg_stroke,
|
||||
},
|
||||
epaint::RectShape::new(
|
||||
outer_rect.expand(visuals.expansion),
|
||||
visuals.rounding,
|
||||
visuals.weak_bg_fill,
|
||||
visuals.bg_stroke,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -235,12 +235,7 @@ impl Frame {
|
||||
stroke,
|
||||
} = *self;
|
||||
|
||||
let frame_shape = Shape::Rect(epaint::RectShape {
|
||||
rect: outer_rect,
|
||||
rounding,
|
||||
fill,
|
||||
stroke,
|
||||
});
|
||||
let frame_shape = Shape::Rect(epaint::RectShape::new(outer_rect, rounding, fill, stroke));
|
||||
|
||||
if shadow == Default::default() {
|
||||
frame_shape
|
||||
|
||||
@@ -22,6 +22,9 @@ use super::*;
|
||||
/// ui.label("Hello World!");
|
||||
/// });
|
||||
/// # });
|
||||
/// ```
|
||||
///
|
||||
/// The previous rectangle used by this window can be obtained through [`crate::Memory::area_rect()`].
|
||||
#[must_use = "You should call .show()"]
|
||||
pub struct Window<'open> {
|
||||
title: WidgetText,
|
||||
|
||||
@@ -568,6 +568,10 @@ pub struct PointerState {
|
||||
/// Used to check for triple-clicks.
|
||||
last_last_click_time: f64,
|
||||
|
||||
/// When was the pointer last moved?
|
||||
/// Used for things like showing hover ui/tooltip with a delay.
|
||||
last_move_time: f64,
|
||||
|
||||
/// All button events that occurred this frame
|
||||
pub(crate) pointer_events: Vec<PointerEvent>,
|
||||
}
|
||||
@@ -587,6 +591,7 @@ impl Default for PointerState {
|
||||
has_moved_too_much_for_a_click: false,
|
||||
last_click_time: std::f64::NEG_INFINITY,
|
||||
last_last_click_time: std::f64::NEG_INFINITY,
|
||||
last_move_time: std::f64::NEG_INFINITY,
|
||||
pointer_events: vec![],
|
||||
}
|
||||
}
|
||||
@@ -711,6 +716,9 @@ impl PointerState {
|
||||
} else {
|
||||
Vec2::default()
|
||||
};
|
||||
if self.velocity != Vec2::ZERO {
|
||||
self.last_move_time = time;
|
||||
}
|
||||
|
||||
self
|
||||
}
|
||||
@@ -790,6 +798,12 @@ impl PointerState {
|
||||
self.velocity != Vec2::ZERO
|
||||
}
|
||||
|
||||
/// How long has it been (in seconds) since the pointer was last moved?
|
||||
#[inline(always)]
|
||||
pub fn time_since_last_movement(&self) -> f64 {
|
||||
self.time - self.last_move_time
|
||||
}
|
||||
|
||||
/// Was any pointer button pressed (`!down -> down`) this frame?
|
||||
/// This can sometimes return `true` even if `any_down() == false`
|
||||
/// because a press can be shorted than one frame.
|
||||
@@ -1035,6 +1049,7 @@ impl PointerState {
|
||||
last_click_time,
|
||||
last_last_click_time,
|
||||
pointer_events,
|
||||
last_move_time,
|
||||
} = self;
|
||||
|
||||
ui.label(format!("latest_pos: {latest_pos:?}"));
|
||||
@@ -1052,6 +1067,7 @@ impl PointerState {
|
||||
));
|
||||
ui.label(format!("last_click_time: {last_click_time:#?}"));
|
||||
ui.label(format!("last_last_click_time: {last_last_click_time:#?}"));
|
||||
ui.label(format!("last_move_time: {last_move_time:#?}"));
|
||||
ui.label(format!("pointer_events: {pointer_events:?}"));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -545,6 +545,11 @@ impl Memory {
|
||||
pub fn reset_areas(&mut self) {
|
||||
self.areas = Default::default();
|
||||
}
|
||||
|
||||
/// Obtain the previous rectangle of an area.
|
||||
pub fn area_rect(&self, id: impl Into<Id>) -> Option<Rect> {
|
||||
self.areas.get(id.into()).map(|state| state.rect())
|
||||
}
|
||||
}
|
||||
|
||||
/// ## Popups
|
||||
|
||||
@@ -311,12 +311,7 @@ impl Painter {
|
||||
fill_color: impl Into<Color32>,
|
||||
stroke: impl Into<Stroke>,
|
||||
) {
|
||||
self.add(RectShape {
|
||||
rect,
|
||||
rounding: rounding.into(),
|
||||
fill: fill_color.into(),
|
||||
stroke: stroke.into(),
|
||||
});
|
||||
self.add(RectShape::new(rect, rounding, fill_color, stroke));
|
||||
}
|
||||
|
||||
pub fn rect_filled(
|
||||
@@ -325,12 +320,7 @@ impl Painter {
|
||||
rounding: impl Into<Rounding>,
|
||||
fill_color: impl Into<Color32>,
|
||||
) {
|
||||
self.add(RectShape {
|
||||
rect,
|
||||
rounding: rounding.into(),
|
||||
fill: fill_color.into(),
|
||||
stroke: Default::default(),
|
||||
});
|
||||
self.add(RectShape::filled(rect, rounding, fill_color));
|
||||
}
|
||||
|
||||
pub fn rect_stroke(
|
||||
@@ -339,12 +329,7 @@ impl Painter {
|
||||
rounding: impl Into<Rounding>,
|
||||
stroke: impl Into<Stroke>,
|
||||
) {
|
||||
self.add(RectShape {
|
||||
rect,
|
||||
rounding: rounding.into(),
|
||||
fill: Default::default(),
|
||||
stroke: stroke.into(),
|
||||
});
|
||||
self.add(RectShape::stroke(rect, rounding, stroke));
|
||||
}
|
||||
|
||||
/// Show an arrow starting at `origin` and going in the direction of `vec`, with the length `vec.length()`.
|
||||
|
||||
@@ -435,6 +435,15 @@ impl Response {
|
||||
}
|
||||
}
|
||||
|
||||
if !self.is_tooltip_open()
|
||||
&& self.ctx.input(|i| i.pointer.time_since_last_movement())
|
||||
< self.ctx.style().interaction.tooltip_delay
|
||||
{
|
||||
// Keep waiting until the mouse has been still for a while
|
||||
self.ctx.request_repaint();
|
||||
return false;
|
||||
}
|
||||
|
||||
// We don't want tooltips of things while we are dragging them,
|
||||
// but we do want tooltips while holding down on an item on a touch screen.
|
||||
if self
|
||||
|
||||
@@ -441,6 +441,9 @@ pub struct Interaction {
|
||||
|
||||
/// If `false`, tooltips will show up anytime you hover anything, even is mouse is still moving
|
||||
pub show_tooltips_only_when_still: bool,
|
||||
|
||||
/// Delay in seconds before showing tooltips after the mouse stops moving
|
||||
pub tooltip_delay: f64,
|
||||
}
|
||||
|
||||
/// Controls the visual style (colors etc) of egui.
|
||||
@@ -762,6 +765,7 @@ impl Default for Interaction {
|
||||
resize_grab_radius_side: 5.0,
|
||||
resize_grab_radius_corner: 10.0,
|
||||
show_tooltips_only_when_still: true,
|
||||
tooltip_delay: 0.0,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1218,6 +1222,7 @@ impl Interaction {
|
||||
resize_grab_radius_side,
|
||||
resize_grab_radius_corner,
|
||||
show_tooltips_only_when_still,
|
||||
tooltip_delay,
|
||||
} = self;
|
||||
ui.add(Slider::new(resize_grab_radius_side, 0.0..=20.0).text("resize_grab_radius_side"));
|
||||
ui.add(
|
||||
@@ -1227,6 +1232,7 @@ impl Interaction {
|
||||
show_tooltips_only_when_still,
|
||||
"Only show tooltips if mouse is still",
|
||||
);
|
||||
ui.add(Slider::new(tooltip_delay, 0.0..=1.0).text("tooltip_delay"));
|
||||
|
||||
ui.vertical_centered(|ui| reset_button(ui, self));
|
||||
}
|
||||
|
||||
@@ -318,12 +318,12 @@ impl<'a> Widget for Checkbox<'a> {
|
||||
// let visuals = ui.style().interact_selectable(&response, *checked); // too colorful
|
||||
let visuals = ui.style().interact(&response);
|
||||
let (small_icon_rect, big_icon_rect) = ui.spacing().icon_rectangles(rect);
|
||||
ui.painter().add(epaint::RectShape {
|
||||
rect: big_icon_rect.expand(visuals.expansion),
|
||||
rounding: visuals.rounding,
|
||||
fill: visuals.bg_fill,
|
||||
stroke: visuals.bg_stroke,
|
||||
});
|
||||
ui.painter().add(epaint::RectShape::new(
|
||||
big_icon_rect.expand(visuals.expansion),
|
||||
visuals.rounding,
|
||||
visuals.bg_fill,
|
||||
visuals.bg_stroke,
|
||||
));
|
||||
|
||||
if *checked {
|
||||
// Check mark:
|
||||
@@ -535,7 +535,7 @@ impl Widget for ImageButton {
|
||||
let selection = ui.visuals().selection;
|
||||
(
|
||||
Vec2::ZERO,
|
||||
Rounding::none(),
|
||||
Rounding::ZERO,
|
||||
selection.bg_fill,
|
||||
selection.stroke,
|
||||
)
|
||||
@@ -552,6 +552,8 @@ impl Widget for ImageButton {
|
||||
Default::default()
|
||||
};
|
||||
|
||||
let image = image.rounding(rounding); // apply rounding to the image
|
||||
|
||||
// Draw frame background (for transparent images):
|
||||
ui.painter()
|
||||
.rect_filled(rect.expand2(expansion), rounding, fill);
|
||||
|
||||
@@ -42,6 +42,7 @@ pub struct Image {
|
||||
tint: Color32,
|
||||
sense: Sense,
|
||||
rotation: Option<(Rot2, Vec2)>,
|
||||
rounding: Rounding,
|
||||
}
|
||||
|
||||
impl Image {
|
||||
@@ -54,6 +55,7 @@ impl Image {
|
||||
tint: Color32::WHITE,
|
||||
sense: Sense::hover(),
|
||||
rotation: None,
|
||||
rounding: Rounding::ZERO,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -89,8 +91,26 @@ impl Image {
|
||||
/// 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.
|
||||
///
|
||||
/// Due to limitations in the current implementation,
|
||||
/// this will turn off rounding of the image.
|
||||
pub fn rotate(mut self, angle: f32, origin: Vec2) -> Self {
|
||||
self.rotation = Some((Rot2::from_angle(angle), origin));
|
||||
self.rounding = Rounding::ZERO; // incompatible with rotation
|
||||
self
|
||||
}
|
||||
|
||||
/// Round the corners of the image.
|
||||
///
|
||||
/// The default is no rounding ([`Rounding::ZERO`]).
|
||||
///
|
||||
/// Due to limitations in the current implementation,
|
||||
/// this will turn off any rotation of the image.
|
||||
pub fn rounding(mut self, rounding: impl Into<Rounding>) -> Self {
|
||||
self.rounding = rounding.into();
|
||||
if self.rounding != Rounding::ZERO {
|
||||
self.rotation = None; // incompatible with rounding
|
||||
}
|
||||
self
|
||||
}
|
||||
}
|
||||
@@ -111,6 +131,7 @@ impl Image {
|
||||
tint,
|
||||
sense: _,
|
||||
rotation,
|
||||
rounding,
|
||||
} = self;
|
||||
|
||||
if *bg_fill != Default::default() {
|
||||
@@ -119,14 +140,27 @@ impl Image {
|
||||
ui.painter().add(Shape::mesh(mesh));
|
||||
}
|
||||
|
||||
{
|
||||
// TODO(emilk): builder pattern for Mesh
|
||||
if let Some((rot, origin)) = rotation {
|
||||
// TODO(emilk): implement this using `PathShape` (add texture support to it).
|
||||
// This will also give us anti-aliasing of rotated images.
|
||||
egui_assert!(
|
||||
*rounding == Rounding::ZERO,
|
||||
"Image had both rounding and rotation. Please pick only one"
|
||||
);
|
||||
|
||||
let mut mesh = Mesh::with_texture(*texture_id);
|
||||
mesh.add_rect_with_uv(rect, *uv, *tint);
|
||||
if let Some((rot, origin)) = rotation {
|
||||
mesh.rotate(*rot, rect.min + *origin * *size);
|
||||
}
|
||||
mesh.rotate(*rot, rect.min + *origin * *size);
|
||||
ui.painter().add(Shape::mesh(mesh));
|
||||
} else {
|
||||
ui.painter().add(RectShape {
|
||||
rect,
|
||||
rounding: *rounding,
|
||||
fill: *tint,
|
||||
stroke: Stroke::NONE,
|
||||
fill_texture_id: *texture_id,
|
||||
uv: *uv,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@ use crate::{Response, Sense, TextStyle, Ui, WidgetText};
|
||||
|
||||
use super::{transform::PlotTransform, GridMark};
|
||||
|
||||
pub(super) type AxisFormatterFn = fn(f64, usize, &RangeInclusive<f64>) -> String;
|
||||
pub(super) type AxisFormatterFn = dyn Fn(f64, usize, &RangeInclusive<f64>) -> String;
|
||||
|
||||
/// X or Y axis.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
@@ -81,7 +81,7 @@ impl From<VPlacement> for Placement {
|
||||
#[derive(Clone)]
|
||||
pub struct AxisHints {
|
||||
pub(super) label: WidgetText,
|
||||
pub(super) formatter: AxisFormatterFn,
|
||||
pub(super) formatter: Arc<AxisFormatterFn>,
|
||||
pub(super) digits: usize,
|
||||
pub(super) placement: Placement,
|
||||
}
|
||||
@@ -98,7 +98,7 @@ impl Default for AxisHints {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
label: Default::default(),
|
||||
formatter: Self::default_formatter,
|
||||
formatter: Arc::new(Self::default_formatter),
|
||||
digits: 5,
|
||||
placement: Placement::LeftBottom,
|
||||
}
|
||||
@@ -111,8 +111,11 @@ impl AxisHints {
|
||||
/// The first parameter of `formatter` is the raw tick value as `f64`.
|
||||
/// The second parameter is the maximum number of characters that fit into y-labels.
|
||||
/// The second parameter of `formatter` is the currently shown range on this axis.
|
||||
pub fn formatter(mut self, fmt: fn(f64, usize, &RangeInclusive<f64>) -> String) -> Self {
|
||||
self.formatter = fmt;
|
||||
pub fn formatter(
|
||||
mut self,
|
||||
fmt: impl Fn(f64, usize, &RangeInclusive<f64>) -> String + 'static,
|
||||
) -> Self {
|
||||
self.formatter = Arc::new(fmt);
|
||||
self
|
||||
}
|
||||
|
||||
|
||||
@@ -127,12 +127,7 @@ impl Bar {
|
||||
};
|
||||
|
||||
let rect = transform.rect_from_values(&self.bounds_min(), &self.bounds_max());
|
||||
let rect = Shape::Rect(RectShape {
|
||||
rect,
|
||||
rounding: Rounding::none(),
|
||||
fill,
|
||||
stroke,
|
||||
});
|
||||
let rect = Shape::Rect(RectShape::new(rect, Rounding::ZERO, fill, stroke));
|
||||
|
||||
shapes.push(rect);
|
||||
}
|
||||
|
||||
@@ -150,12 +150,7 @@ impl BoxElem {
|
||||
&self.point_at(self.argument - self.box_width / 2.0, self.spread.quartile1),
|
||||
&self.point_at(self.argument + self.box_width / 2.0, self.spread.quartile3),
|
||||
);
|
||||
let rect = Shape::Rect(RectShape {
|
||||
rect,
|
||||
rounding: Rounding::none(),
|
||||
fill,
|
||||
stroke,
|
||||
});
|
||||
let rect = Shape::Rect(RectShape::new(rect, Rounding::ZERO, fill, stroke));
|
||||
shapes.push(rect);
|
||||
|
||||
let line_between = |v1, v2| {
|
||||
|
||||
@@ -613,24 +613,32 @@ impl Plot {
|
||||
|
||||
/// Specify custom formatter for ticks on the main X-axis.
|
||||
///
|
||||
/// The first parameter of `fmt` is the raw tick value as `f64`.
|
||||
/// The second parameter is the maximum requested number of characters per tick label.
|
||||
/// The second parameter of `fmt` is the currently shown range on this axis.
|
||||
pub fn x_axis_formatter(mut self, fmt: fn(f64, usize, &RangeInclusive<f64>) -> String) -> Self {
|
||||
/// Arguments of `fmt`:
|
||||
/// * raw tick value as `f64`.
|
||||
/// * maximum requested number of characters per tick label.
|
||||
/// * currently shown range on this axis.
|
||||
pub fn x_axis_formatter(
|
||||
mut self,
|
||||
fmt: impl Fn(f64, usize, &RangeInclusive<f64>) -> String + 'static,
|
||||
) -> Self {
|
||||
if let Some(main) = self.x_axes.first_mut() {
|
||||
main.formatter = fmt;
|
||||
main.formatter = Arc::new(fmt);
|
||||
}
|
||||
self
|
||||
}
|
||||
|
||||
/// Specify custom formatter for ticks on the main Y-axis.
|
||||
///
|
||||
/// The first parameter of `formatter` is the raw tick value as `f64`.
|
||||
/// The second parameter is the maximum requested number of characters per tick label.
|
||||
/// The second parameter of `formatter` is the currently shown range on this axis.
|
||||
pub fn y_axis_formatter(mut self, fmt: fn(f64, usize, &RangeInclusive<f64>) -> String) -> Self {
|
||||
/// Arguments of `fmt`:
|
||||
/// * raw tick value as `f64`.
|
||||
/// * maximum requested number of characters per tick label.
|
||||
/// * currently shown range on this axis.
|
||||
pub fn y_axis_formatter(
|
||||
mut self,
|
||||
fmt: impl Fn(f64, usize, &RangeInclusive<f64>) -> String + 'static,
|
||||
) -> Self {
|
||||
if let Some(main) = self.y_axes.first_mut() {
|
||||
main.formatter = fmt;
|
||||
main.formatter = Arc::new(fmt);
|
||||
}
|
||||
self
|
||||
}
|
||||
@@ -864,12 +872,14 @@ impl Plot {
|
||||
|
||||
// Background
|
||||
if show_background {
|
||||
ui.painter().with_clip_rect(rect).add(epaint::RectShape {
|
||||
rect,
|
||||
rounding: Rounding::same(2.0),
|
||||
fill: ui.visuals().extreme_bg_color,
|
||||
stroke: ui.visuals().widgets.noninteractive.bg_stroke,
|
||||
});
|
||||
ui.painter()
|
||||
.with_clip_rect(rect)
|
||||
.add(epaint::RectShape::new(
|
||||
rect,
|
||||
Rounding::same(2.0),
|
||||
ui.visuals().extreme_bg_color,
|
||||
ui.visuals().widgets.noninteractive.bg_stroke,
|
||||
));
|
||||
}
|
||||
|
||||
// --- Legend ---
|
||||
|
||||
@@ -368,31 +368,27 @@ impl<'t> TextEdit<'t> {
|
||||
let frame_rect = frame_rect.expand(visuals.expansion);
|
||||
let shape = if is_mutable {
|
||||
if output.response.has_focus() {
|
||||
epaint::RectShape {
|
||||
rect: frame_rect,
|
||||
rounding: visuals.rounding,
|
||||
// fill: ui.visuals().selection.bg_fill,
|
||||
fill: ui.visuals().extreme_bg_color,
|
||||
stroke: ui.visuals().selection.stroke,
|
||||
}
|
||||
epaint::RectShape::new(
|
||||
frame_rect,
|
||||
visuals.rounding,
|
||||
ui.visuals().extreme_bg_color,
|
||||
ui.visuals().selection.stroke,
|
||||
)
|
||||
} else {
|
||||
epaint::RectShape {
|
||||
rect: frame_rect,
|
||||
rounding: visuals.rounding,
|
||||
fill: ui.visuals().extreme_bg_color,
|
||||
stroke: visuals.bg_stroke, // TODO(emilk): we want to show something here, or a text-edit field doesn't "pop".
|
||||
}
|
||||
epaint::RectShape::new(
|
||||
frame_rect,
|
||||
visuals.rounding,
|
||||
ui.visuals().extreme_bg_color,
|
||||
visuals.bg_stroke, // TODO(emilk): we want to show something here, or a text-edit field doesn't "pop".
|
||||
)
|
||||
}
|
||||
} else {
|
||||
let visuals = &ui.style().visuals.widgets.inactive;
|
||||
epaint::RectShape {
|
||||
rect: frame_rect,
|
||||
rounding: visuals.rounding,
|
||||
// fill: ui.visuals().extreme_bg_color,
|
||||
// fill: visuals.bg_fill,
|
||||
fill: Color32::TRANSPARENT,
|
||||
stroke: visuals.bg_stroke, // TODO(emilk): we want to show something here, or a text-edit field doesn't "pop".
|
||||
}
|
||||
epaint::RectShape::stroke(
|
||||
frame_rect,
|
||||
visuals.rounding,
|
||||
visuals.bg_stroke, // TODO(emilk): we want to show something here, or a text-edit field doesn't "pop".
|
||||
)
|
||||
};
|
||||
|
||||
ui.painter().set(where_to_put_background, shape);
|
||||
|
||||
Reference in New Issue
Block a user