Merge branch 'master' into zlayer
This commit is contained in:
commit
db46385073
6 changed files with 117 additions and 34 deletions
|
@ -8,6 +8,8 @@ NOTE: [`epaint`](crates/epaint/CHANGELOG.md), [`eframe`](crates/eframe/CHANGELOG
|
||||||
### Added ⭐
|
### Added ⭐
|
||||||
* `Event::Key` now has a `repeat` field that is set to `true` if the event was the result of a key-repeat ([#2435](https://github.com/emilk/egui/pull/2435)).
|
* `Event::Key` now has a `repeat` field that is set to `true` if the event was the result of a key-repeat ([#2435](https://github.com/emilk/egui/pull/2435)).
|
||||||
* Add `Slider::drag_value_speed`, which lets you ask for finer precision when dragging the slider value rather than the actual slider.
|
* Add `Slider::drag_value_speed`, which lets you ask for finer precision when dragging the slider value rather than the actual slider.
|
||||||
|
* Improved plot grid appearance ([#2412](https://github.com/emilk/egui/pull/2412)).
|
||||||
|
* Add `Memory::any_popup_open`, which returns true if any popup is currently open ([#2464](https://github.com/emilk/egui/pull/2464)).
|
||||||
|
|
||||||
### Fixed 🐛
|
### Fixed 🐛
|
||||||
* Expose `TextEdit`'s multiline flag to AccessKit ([#2448](https://github.com/emilk/egui/pull/2448)).
|
* Expose `TextEdit`'s multiline flag to AccessKit ([#2448](https://github.com/emilk/egui/pull/2448)).
|
||||||
|
|
|
@ -265,21 +265,35 @@ fn combo_box_dyn<'c, R>(
|
||||||
let button_response = button_frame(ui, button_id, is_popup_open, Sense::click(), |ui| {
|
let button_response = button_frame(ui, button_id, is_popup_open, Sense::click(), |ui| {
|
||||||
// We don't want to change width when user selects something new
|
// We don't want to change width when user selects something new
|
||||||
let full_minimum_width = if wrap_enabled {
|
let full_minimum_width = if wrap_enabled {
|
||||||
ui.available_width() - ui.spacing().item_spacing.x * 2.0
|
// Currently selected value's text will be wrapped if needed, so occupy the available width.
|
||||||
|
ui.available_width()
|
||||||
} else {
|
} else {
|
||||||
|
// Occupy at least the minimum width assigned to Slider and ComboBox.
|
||||||
ui.spacing().slider_width - 2.0 * margin.x
|
ui.spacing().slider_width - 2.0 * margin.x
|
||||||
};
|
};
|
||||||
let icon_size = Vec2::splat(ui.spacing().icon_width);
|
let icon_size = Vec2::splat(ui.spacing().icon_width);
|
||||||
let wrap_width = if wrap_enabled {
|
let wrap_width = if wrap_enabled {
|
||||||
|
// Use the available width, currently selected value's text will be wrapped if exceeds this value.
|
||||||
ui.available_width() - ui.spacing().item_spacing.x - icon_size.x
|
ui.available_width() - ui.spacing().item_spacing.x - icon_size.x
|
||||||
} else {
|
} else {
|
||||||
|
// Use all the width necessary to display the currently selected value's text.
|
||||||
f32::INFINITY
|
f32::INFINITY
|
||||||
};
|
};
|
||||||
|
|
||||||
let galley =
|
let galley =
|
||||||
selected_text.into_galley(ui, Some(wrap_enabled), wrap_width, TextStyle::Button);
|
selected_text.into_galley(ui, Some(wrap_enabled), wrap_width, TextStyle::Button);
|
||||||
|
|
||||||
let width = galley.size().x + ui.spacing().item_spacing.x + icon_size.x;
|
// The width necessary to contain the whole widget with the currently selected value's text.
|
||||||
|
let width = if wrap_enabled {
|
||||||
|
full_minimum_width
|
||||||
|
} else {
|
||||||
|
// Occupy at least the minimum width needed to contain the widget with the currently selected value's text.
|
||||||
|
galley.size().x + ui.spacing().item_spacing.x + icon_size.x
|
||||||
|
};
|
||||||
|
|
||||||
|
// Case : wrap_enabled : occupy all the available width.
|
||||||
|
// Case : !wrap_enabled : occupy at least the minimum width assigned to Slider and ComboBox,
|
||||||
|
// increase if the currently selected value needs additional horizontal space to fully display its text (up to wrap_width (f32::INFINITY)).
|
||||||
let width = width.at_least(full_minimum_width);
|
let width = width.at_least(full_minimum_width);
|
||||||
let height = galley.size().y.max(icon_size.y);
|
let height = galley.size().y.max(icon_size.y);
|
||||||
|
|
||||||
|
|
|
@ -479,6 +479,10 @@ impl Memory {
|
||||||
self.popup == Some(popup_id) || self.everything_is_visible()
|
self.popup == Some(popup_id) || self.everything_is_visible()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn any_popup_open(&self) -> bool {
|
||||||
|
self.popup.is_some() || self.everything_is_visible()
|
||||||
|
}
|
||||||
|
|
||||||
pub fn open_popup(&mut self, popup_id: Id) {
|
pub fn open_popup(&mut self, popup_id: Id) {
|
||||||
self.popup = Some(popup_id);
|
self.popup = Some(popup_id);
|
||||||
}
|
}
|
||||||
|
|
|
@ -292,6 +292,7 @@ pub struct Plot {
|
||||||
show_background: bool,
|
show_background: bool,
|
||||||
show_axes: [bool; 2],
|
show_axes: [bool; 2],
|
||||||
grid_spacers: [GridSpacer; 2],
|
grid_spacers: [GridSpacer; 2],
|
||||||
|
sharp_grid_lines: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Plot {
|
impl Plot {
|
||||||
|
@ -331,6 +332,7 @@ impl Plot {
|
||||||
show_background: true,
|
show_background: true,
|
||||||
show_axes: [true; 2],
|
show_axes: [true; 2],
|
||||||
grid_spacers: [log_grid_spacer(10), log_grid_spacer(10)],
|
grid_spacers: [log_grid_spacer(10), log_grid_spacer(10)],
|
||||||
|
sharp_grid_lines: true,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -617,6 +619,13 @@ impl Plot {
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Round grid positions to full pixels to avoid aliasing. Improves plot appearance but might have an
|
||||||
|
/// undesired effect when shifting the plot bounds. Enabled by default.
|
||||||
|
pub fn sharp_grid_lines(mut self, enabled: bool) -> Self {
|
||||||
|
self.sharp_grid_lines = enabled;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
/// Resets the plot.
|
/// Resets the plot.
|
||||||
pub fn reset(mut self) -> Self {
|
pub fn reset(mut self) -> Self {
|
||||||
self.reset = true;
|
self.reset = true;
|
||||||
|
@ -663,6 +672,7 @@ impl Plot {
|
||||||
linked_axes,
|
linked_axes,
|
||||||
linked_cursors,
|
linked_cursors,
|
||||||
grid_spacers,
|
grid_spacers,
|
||||||
|
sharp_grid_lines,
|
||||||
} = self;
|
} = self;
|
||||||
|
|
||||||
// Determine the size of the plot in the UI
|
// Determine the size of the plot in the UI
|
||||||
|
@ -965,6 +975,7 @@ impl Plot {
|
||||||
draw_cursor_x: linked_cursors.as_ref().map_or(false, |group| group.link_x),
|
draw_cursor_x: linked_cursors.as_ref().map_or(false, |group| group.link_x),
|
||||||
draw_cursor_y: linked_cursors.as_ref().map_or(false, |group| group.link_y),
|
draw_cursor_y: linked_cursors.as_ref().map_or(false, |group| group.link_y),
|
||||||
draw_cursors,
|
draw_cursors,
|
||||||
|
sharp_grid_lines,
|
||||||
};
|
};
|
||||||
let plot_cursors = prepared.ui(ui, &response);
|
let plot_cursors = prepared.ui(ui, &response);
|
||||||
|
|
||||||
|
@ -1290,18 +1301,30 @@ struct PreparedPlot {
|
||||||
draw_cursor_x: bool,
|
draw_cursor_x: bool,
|
||||||
draw_cursor_y: bool,
|
draw_cursor_y: bool,
|
||||||
draw_cursors: Vec<Cursor>,
|
draw_cursors: Vec<Cursor>,
|
||||||
|
sharp_grid_lines: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PreparedPlot {
|
impl PreparedPlot {
|
||||||
fn ui(self, ui: &mut Ui, response: &Response) -> Vec<Cursor> {
|
fn ui(self, ui: &mut Ui, response: &Response) -> Vec<Cursor> {
|
||||||
let mut shapes = Vec::new();
|
let mut axes_shapes = Vec::new();
|
||||||
|
|
||||||
for d in 0..2 {
|
for d in 0..2 {
|
||||||
if self.show_axes[d] {
|
if self.show_axes[d] {
|
||||||
self.paint_axis(ui, d, &mut shapes);
|
self.paint_axis(
|
||||||
|
ui,
|
||||||
|
d,
|
||||||
|
self.show_axes[1 - d],
|
||||||
|
&mut axes_shapes,
|
||||||
|
self.sharp_grid_lines,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Sort the axes by strength so that those with higher strength are drawn in front.
|
||||||
|
axes_shapes.sort_by(|(_, strength1), (_, strength2)| strength1.total_cmp(strength2));
|
||||||
|
|
||||||
|
let mut shapes = axes_shapes.into_iter().map(|(shape, _)| shape).collect();
|
||||||
|
|
||||||
let transform = &self.transform;
|
let transform = &self.transform;
|
||||||
|
|
||||||
let mut plot_ui = ui.child_ui(*transform.frame(), Layout::default());
|
let mut plot_ui = ui.child_ui(*transform.frame(), Layout::default());
|
||||||
|
@ -1369,7 +1392,14 @@ impl PreparedPlot {
|
||||||
cursors
|
cursors
|
||||||
}
|
}
|
||||||
|
|
||||||
fn paint_axis(&self, ui: &Ui, axis: usize, shapes: &mut Vec<Shape>) {
|
fn paint_axis(
|
||||||
|
&self,
|
||||||
|
ui: &Ui,
|
||||||
|
axis: usize,
|
||||||
|
other_axis_shown: bool,
|
||||||
|
shapes: &mut Vec<(Shape, f32)>,
|
||||||
|
sharp_grid_lines: bool,
|
||||||
|
) {
|
||||||
let Self {
|
let Self {
|
||||||
transform,
|
transform,
|
||||||
axis_formatters,
|
axis_formatters,
|
||||||
|
@ -1408,29 +1438,35 @@ impl PreparedPlot {
|
||||||
let pos_in_gui = transform.position_from_point(&value);
|
let pos_in_gui = transform.position_from_point(&value);
|
||||||
let spacing_in_points = (transform.dpos_dvalue()[axis] * step.step_size).abs() as f32;
|
let spacing_in_points = (transform.dpos_dvalue()[axis] * step.step_size).abs() as f32;
|
||||||
|
|
||||||
let line_alpha = remap_clamp(
|
if spacing_in_points > MIN_LINE_SPACING_IN_POINTS as f32 {
|
||||||
spacing_in_points,
|
let line_strength = remap_clamp(
|
||||||
(MIN_LINE_SPACING_IN_POINTS as f32)..=300.0,
|
spacing_in_points,
|
||||||
0.0..=0.15,
|
MIN_LINE_SPACING_IN_POINTS as f32..=300.0,
|
||||||
);
|
0.0..=1.0,
|
||||||
|
);
|
||||||
|
|
||||||
if line_alpha > 0.0 {
|
let line_color = color_from_contrast(ui, line_strength);
|
||||||
let line_color = color_from_alpha(ui, line_alpha);
|
|
||||||
|
|
||||||
let mut p0 = pos_in_gui;
|
let mut p0 = pos_in_gui;
|
||||||
let mut p1 = pos_in_gui;
|
let mut p1 = pos_in_gui;
|
||||||
p0[1 - axis] = transform.frame().min[1 - axis];
|
p0[1 - axis] = transform.frame().min[1 - axis];
|
||||||
p1[1 - axis] = transform.frame().max[1 - axis];
|
p1[1 - axis] = transform.frame().max[1 - axis];
|
||||||
// Round to avoid aliasing
|
if sharp_grid_lines {
|
||||||
p0 = ui.ctx().round_pos_to_pixels(p0);
|
// Round to avoid aliasing
|
||||||
p1 = ui.ctx().round_pos_to_pixels(p1);
|
p0 = ui.ctx().round_pos_to_pixels(p0);
|
||||||
shapes.push(Shape::line_segment([p0, p1], Stroke::new(1.0, line_color)));
|
p1 = ui.ctx().round_pos_to_pixels(p1);
|
||||||
|
}
|
||||||
|
shapes.push((
|
||||||
|
Shape::line_segment([p0, p1], Stroke::new(1.0, line_color)),
|
||||||
|
line_strength,
|
||||||
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
let text_alpha = remap_clamp(spacing_in_points, 40.0..=150.0, 0.0..=0.4);
|
const MIN_TEXT_SPACING: f32 = 40.0;
|
||||||
|
if spacing_in_points > MIN_TEXT_SPACING {
|
||||||
if text_alpha > 0.0 {
|
let text_strength =
|
||||||
let color = color_from_alpha(ui, text_alpha);
|
remap_clamp(spacing_in_points, MIN_TEXT_SPACING..=150.0, 0.0..=1.0);
|
||||||
|
let color = color_from_contrast(ui, text_strength);
|
||||||
|
|
||||||
let text: String = if let Some(formatter) = axis_formatters[axis].as_deref() {
|
let text: String = if let Some(formatter) = axis_formatters[axis].as_deref() {
|
||||||
formatter(value_main, &axis_range)
|
formatter(value_main, &axis_range)
|
||||||
|
@ -1438,8 +1474,11 @@ impl PreparedPlot {
|
||||||
emath::round_to_decimals(value_main, 5).to_string() // hack
|
emath::round_to_decimals(value_main, 5).to_string() // hack
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Skip origin label for y-axis if x-axis is already showing it (otherwise displayed twice)
|
||||||
|
let skip_origin_y = axis == 1 && other_axis_shown && value_main == 0.0;
|
||||||
|
|
||||||
// Custom formatters can return empty string to signal "no label at this resolution"
|
// Custom formatters can return empty string to signal "no label at this resolution"
|
||||||
if !text.is_empty() {
|
if !text.is_empty() && !skip_origin_y {
|
||||||
let galley = ui.painter().layout_no_wrap(text, font_id.clone(), color);
|
let galley = ui.painter().layout_no_wrap(text, font_id.clone(), color);
|
||||||
|
|
||||||
let mut text_pos = pos_in_gui + vec2(1.0, -galley.size().y);
|
let mut text_pos = pos_in_gui + vec2(1.0, -galley.size().y);
|
||||||
|
@ -1449,17 +1488,20 @@ impl PreparedPlot {
|
||||||
.at_most(transform.frame().max[1 - axis] - galley.size()[1 - axis] - 2.0)
|
.at_most(transform.frame().max[1 - axis] - galley.size()[1 - axis] - 2.0)
|
||||||
.at_least(transform.frame().min[1 - axis] + 1.0);
|
.at_least(transform.frame().min[1 - axis] + 1.0);
|
||||||
|
|
||||||
shapes.push(Shape::galley(text_pos, galley));
|
shapes.push((Shape::galley(text_pos, galley), text_strength));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn color_from_alpha(ui: &Ui, alpha: f32) -> Color32 {
|
fn color_from_contrast(ui: &Ui, contrast: f32) -> Color32 {
|
||||||
if ui.visuals().dark_mode {
|
let bg = ui.visuals().extreme_bg_color;
|
||||||
Rgba::from_white_alpha(alpha).into()
|
let fg = ui.visuals().widgets.open.fg_stroke.color;
|
||||||
} else {
|
let mix = 0.5 * contrast.sqrt();
|
||||||
Rgba::from_black_alpha((4.0 * alpha).at_most(1.0)).into()
|
Color32::from_rgb(
|
||||||
}
|
lerp((bg.r() as f32)..=(fg.r() as f32), mix) as u8,
|
||||||
|
lerp((bg.g() as f32)..=(fg.g() as f32), mix) as u8,
|
||||||
|
lerp((bg.b() as f32)..=(fg.b() as f32), mix) as u8,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -6,6 +6,7 @@ All notable changes to the epaint crate will be documented in this file.
|
||||||
* Improve the look of thin white lines ([#2437](https://github.com/emilk/egui/pull/2437)).
|
* Improve the look of thin white lines ([#2437](https://github.com/emilk/egui/pull/2437)).
|
||||||
* Don't render `\r` (Carriage Return) ([#2452](https://github.com/emilk/egui/pull/2452)).
|
* Don't render `\r` (Carriage Return) ([#2452](https://github.com/emilk/egui/pull/2452)).
|
||||||
* Fix bug in `Mesh::split_to_u16` ([#2459](https://github.com/emilk/egui/pull/2459)).
|
* Fix bug in `Mesh::split_to_u16` ([#2459](https://github.com/emilk/egui/pull/2459)).
|
||||||
|
* Improve rendering of very thin rectangles.
|
||||||
|
|
||||||
|
|
||||||
## 0.20.0 - 2022-12-08
|
## 0.20.0 - 2022-12-08
|
||||||
|
|
|
@ -1309,12 +1309,32 @@ impl Tessellator {
|
||||||
rect.min = rect.min.at_least(pos2(-1e7, -1e7));
|
rect.min = rect.min.at_least(pos2(-1e7, -1e7));
|
||||||
rect.max = rect.max.at_most(pos2(1e7, 1e7));
|
rect.max = rect.max.at_most(pos2(1e7, 1e7));
|
||||||
|
|
||||||
let path = &mut self.scratchpad_path;
|
if rect.width() < self.feathering {
|
||||||
path.clear();
|
// Very thin - approximate by a vertial line-segment:
|
||||||
path::rounded_rectangle(&mut self.scratchpad_points, rect, rounding);
|
let line = [rect.center_top(), rect.center_bottom()];
|
||||||
path.add_line_loop(&self.scratchpad_points);
|
if fill != Color32::TRANSPARENT {
|
||||||
path.fill(self.feathering, fill, out);
|
self.tessellate_line(line, Stroke::new(rect.width(), fill), out);
|
||||||
path.stroke_closed(self.feathering, stroke, out);
|
}
|
||||||
|
if !stroke.is_empty() {
|
||||||
|
self.tessellate_line(line, stroke, out);
|
||||||
|
}
|
||||||
|
} else if rect.height() < self.feathering {
|
||||||
|
// Very thin - approximate by a horizontal line-segment:
|
||||||
|
let line = [rect.left_center(), rect.right_center()];
|
||||||
|
if fill != Color32::TRANSPARENT {
|
||||||
|
self.tessellate_line(line, Stroke::new(rect.width(), fill), out);
|
||||||
|
}
|
||||||
|
if !stroke.is_empty() {
|
||||||
|
self.tessellate_line(line, stroke, out);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
let path = &mut self.scratchpad_path;
|
||||||
|
path.clear();
|
||||||
|
path::rounded_rectangle(&mut self.scratchpad_points, rect, rounding);
|
||||||
|
path.add_line_loop(&self.scratchpad_points);
|
||||||
|
path.fill(self.feathering, fill, out);
|
||||||
|
path.stroke_closed(self.feathering, stroke, out);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Tessellate a single [`TextShape`] into a [`Mesh`].
|
/// Tessellate a single [`TextShape`] into a [`Mesh`].
|
||||||
|
|
Loading…
Reference in a new issue