mirror of
https://github.com/emilk/egui.git
synced 2026-09-02 06:40:06 -04:00
Merge remote-tracking branch 'egui/master' into dynamic-grid
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "egui_demo_lib"
|
||||
version = "0.16.0"
|
||||
version = "0.17.0"
|
||||
authors = ["Emil Ernerfeldt <emil.ernerfeldt@gmail.com>"]
|
||||
description = "Example library for egui"
|
||||
edition = "2021"
|
||||
@@ -28,7 +28,7 @@ extra_debug_asserts = ["egui/extra_debug_asserts"]
|
||||
extra_asserts = ["egui/extra_asserts"]
|
||||
|
||||
datetime = ["egui_extras/chrono", "chrono"]
|
||||
http = ["ehttp", "image", "poll-promise"]
|
||||
http = ["egui_extras", "ehttp", "image", "poll-promise"]
|
||||
persistence = [
|
||||
"egui/persistence",
|
||||
"epi/persistence",
|
||||
@@ -40,15 +40,18 @@ syntax_highlighting = ["syntect"]
|
||||
|
||||
|
||||
[dependencies]
|
||||
egui = { version = "0.16.0", path = "../egui", default-features = false }
|
||||
epi = { version = "0.16.0", path = "../epi" }
|
||||
egui_extras = { version = "0.16.0", path = "../egui_extras" }
|
||||
egui = { version = "0.17.0", path = "../egui", default-features = false }
|
||||
epi = { version = "0.17.0", path = "../epi" }
|
||||
|
||||
chrono = { version = "0.4", features = ["js-sys", "wasmbind"], optional = true }
|
||||
enum-map = { version = "2", features = ["serde"] }
|
||||
unicode_names2 = { version = "0.4.0", default-features = false }
|
||||
unicode_names2 = { version = "0.5.0", default-features = false }
|
||||
|
||||
# feature "http":
|
||||
egui_extras = { version = "0.17.0", path = "../egui_extras", features = [
|
||||
"image",
|
||||
"datepicker",
|
||||
], optional = true }
|
||||
ehttp = { version = "0.2.0", optional = true }
|
||||
image = { version = "0.24", default-features = false, features = [
|
||||
"jpeg",
|
||||
|
||||
@@ -13,10 +13,10 @@ pub fn criterion_benchmark(c: &mut Criterion) {
|
||||
// The most end-to-end benchmark.
|
||||
c.bench_function("demo_with_tessellate__realistic", |b| {
|
||||
b.iter(|| {
|
||||
let (_output, shapes) = ctx.run(RawInput::default(), |ctx| {
|
||||
let full_output = ctx.run(RawInput::default(), |ctx| {
|
||||
demo_windows.ui(ctx);
|
||||
});
|
||||
ctx.tessellate(shapes)
|
||||
ctx.tessellate(full_output.shapes)
|
||||
})
|
||||
});
|
||||
|
||||
@@ -28,11 +28,11 @@ pub fn criterion_benchmark(c: &mut Criterion) {
|
||||
})
|
||||
});
|
||||
|
||||
let (_output, shapes) = ctx.run(RawInput::default(), |ctx| {
|
||||
let full_output = ctx.run(RawInput::default(), |ctx| {
|
||||
demo_windows.ui(ctx);
|
||||
});
|
||||
c.bench_function("demo_only_tessellate", |b| {
|
||||
b.iter(|| ctx.tessellate(shapes.clone()))
|
||||
b.iter(|| ctx.tessellate(full_output.shapes.clone()))
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -83,6 +83,10 @@ impl super::View for CodeExample {
|
||||
fn ui(&mut self, ui: &mut egui::Ui) {
|
||||
use crate::syntax_highlighting::code_view_ui;
|
||||
|
||||
ui.vertical_centered(|ui| {
|
||||
ui.add(crate::__egui_github_link_file!());
|
||||
});
|
||||
|
||||
code_view_ui(
|
||||
ui,
|
||||
r"
|
||||
|
||||
@@ -105,6 +105,9 @@ impl super::View for ContextMenus {
|
||||
});
|
||||
});
|
||||
});
|
||||
ui.vertical_centered(|ui| {
|
||||
ui.add(crate::__egui_github_link_file!());
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -16,6 +16,7 @@ struct Demos {
|
||||
impl Default for Demos {
|
||||
fn default() -> Self {
|
||||
Self::from_demos(vec![
|
||||
Box::new(super::paint_bezier::PaintBezier::default()),
|
||||
Box::new(super::code_editor::CodeEditor::default()),
|
||||
Box::new(super::code_example::CodeExample::default()),
|
||||
Box::new(super::context_menu::ContextMenus::default()),
|
||||
@@ -26,7 +27,6 @@ impl Default for Demos {
|
||||
Box::new(super::MiscDemoWindow::default()),
|
||||
Box::new(super::multi_touch::MultiTouch::default()),
|
||||
Box::new(super::painting::Painting::default()),
|
||||
Box::new(super::paint_bezier::PaintBezier::default()),
|
||||
Box::new(super::plot_demo::PlotDemo::default()),
|
||||
Box::new(super::scrolling::Scrolling::default()),
|
||||
Box::new(super::sliders::Sliders::default()),
|
||||
|
||||
@@ -31,6 +31,10 @@ impl super::Demo for FontBook {
|
||||
|
||||
impl super::View for FontBook {
|
||||
fn ui(&mut self, ui: &mut egui::Ui) {
|
||||
ui.vertical_centered(|ui| {
|
||||
ui.add(crate::__egui_github_link_file!());
|
||||
});
|
||||
|
||||
ui.label(format!(
|
||||
"The selected font supports {} characters.",
|
||||
self.named_chars
|
||||
|
||||
@@ -1,250 +1,167 @@
|
||||
use egui::emath::RectTransform;
|
||||
use egui::epaint::{CircleShape, CubicBezierShape, QuadraticBezierShape};
|
||||
use egui::epaint::{CubicBezierShape, PathShape, QuadraticBezierShape};
|
||||
use egui::*;
|
||||
|
||||
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
|
||||
#[cfg_attr(feature = "serde", serde(default))]
|
||||
pub struct PaintBezier {
|
||||
/// Current bezier curve degree, it can be 3, 4.
|
||||
bezier: usize,
|
||||
/// Track the bezier degree before change in order to clean the remaining points.
|
||||
degree_backup: usize,
|
||||
/// Points already clicked. once it reaches the 'bezier' degree, it will be pushed into the 'shapes'
|
||||
points: Vec<Pos2>,
|
||||
/// Track last points set in order to draw auxiliary lines.
|
||||
backup_points: Vec<Pos2>,
|
||||
/// Quadratic shapes already drawn.
|
||||
q_shapes: Vec<QuadraticBezierShape>,
|
||||
/// Cubic shapes already drawn.
|
||||
/// Since `Shape` can't be 'serialized', we can't use Shape as variable type.
|
||||
c_shapes: Vec<CubicBezierShape>,
|
||||
/// Bézier curve degree, it can be 3, 4.
|
||||
degree: usize,
|
||||
/// The control points. The [`Self::degree`] first of them are used.
|
||||
control_points: [Pos2; 4],
|
||||
|
||||
/// Stroke for Bézier curve.
|
||||
stroke: Stroke,
|
||||
|
||||
/// Fill for Bézier curve.
|
||||
fill: Color32,
|
||||
|
||||
/// Stroke for auxiliary lines.
|
||||
aux_stroke: Stroke,
|
||||
/// Stroke for bezier curve.
|
||||
stroke: Stroke,
|
||||
/// Fill for bezier curve.
|
||||
fill: Color32,
|
||||
/// The curve should be closed or not.
|
||||
closed: bool,
|
||||
/// Display the bounding box or not.
|
||||
show_bounding_box: bool,
|
||||
/// Storke for the bounding box.
|
||||
|
||||
bounding_box_stroke: Stroke,
|
||||
}
|
||||
|
||||
impl Default for PaintBezier {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
bezier: 4, // default bezier degree, a cubic bezier curve
|
||||
degree_backup: 4,
|
||||
points: Default::default(),
|
||||
backup_points: Default::default(),
|
||||
q_shapes: Default::default(),
|
||||
c_shapes: Default::default(),
|
||||
aux_stroke: Stroke::new(1.0, Color32::RED),
|
||||
degree: 4,
|
||||
control_points: [
|
||||
pos2(50.0, 50.0),
|
||||
pos2(60.0, 250.0),
|
||||
pos2(200.0, 200.0),
|
||||
pos2(250.0, 50.0),
|
||||
],
|
||||
stroke: Stroke::new(1.0, Color32::LIGHT_BLUE),
|
||||
fill: Default::default(),
|
||||
closed: false,
|
||||
show_bounding_box: false,
|
||||
bounding_box_stroke: Stroke::new(1.0, Color32::LIGHT_GREEN),
|
||||
fill: Color32::from_rgb(50, 100, 150).linear_multiply(0.25),
|
||||
aux_stroke: Stroke::new(1.0, Color32::RED.linear_multiply(0.25)),
|
||||
bounding_box_stroke: Stroke::new(0.0, Color32::LIGHT_GREEN.linear_multiply(0.25)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl PaintBezier {
|
||||
pub fn ui_control(&mut self, ui: &mut egui::Ui) -> egui::Response {
|
||||
ui.horizontal(|ui| {
|
||||
ui.vertical(|ui| {
|
||||
egui::stroke_ui(ui, &mut self.stroke, "Curve Stroke");
|
||||
egui::stroke_ui(ui, &mut self.aux_stroke, "Auxiliary Stroke");
|
||||
ui.horizontal(|ui| {
|
||||
ui.label("Fill Color:");
|
||||
if ui.color_edit_button_srgba(&mut self.fill).changed()
|
||||
&& self.fill != Color32::TRANSPARENT
|
||||
{
|
||||
self.closed = true;
|
||||
}
|
||||
if ui.checkbox(&mut self.closed, "Closed").clicked() && !self.closed {
|
||||
self.fill = Color32::TRANSPARENT;
|
||||
}
|
||||
});
|
||||
egui::stroke_ui(ui, &mut self.bounding_box_stroke, "Bounding Box Stroke");
|
||||
pub fn ui_control(&mut self, ui: &mut egui::Ui) {
|
||||
ui.collapsing("Colors", |ui| {
|
||||
ui.horizontal(|ui| {
|
||||
ui.label("Fill color:");
|
||||
ui.color_edit_button_srgba(&mut self.fill);
|
||||
});
|
||||
egui::stroke_ui(ui, &mut self.stroke, "Curve Stroke");
|
||||
egui::stroke_ui(ui, &mut self.aux_stroke, "Auxiliary Stroke");
|
||||
egui::stroke_ui(ui, &mut self.bounding_box_stroke, "Bounding Box Stroke");
|
||||
});
|
||||
|
||||
ui.separator();
|
||||
ui.vertical(|ui| {
|
||||
{
|
||||
let mut tessellation_options = *(ui.ctx().tessellation_options());
|
||||
let tessellation_options = &mut tessellation_options;
|
||||
tessellation_options.ui(ui);
|
||||
let mut new_tessellation_options = ui.ctx().tessellation_options();
|
||||
*new_tessellation_options = *tessellation_options;
|
||||
}
|
||||
ui.collapsing("Global tessellation options", |ui| {
|
||||
let mut tessellation_options = *(ui.ctx().tessellation_options());
|
||||
let tessellation_options = &mut tessellation_options;
|
||||
tessellation_options.ui(ui);
|
||||
let mut new_tessellation_options = ui.ctx().tessellation_options();
|
||||
*new_tessellation_options = *tessellation_options;
|
||||
});
|
||||
|
||||
ui.checkbox(&mut self.show_bounding_box, "Bounding Box");
|
||||
});
|
||||
ui.separator();
|
||||
ui.vertical(|ui| {
|
||||
if ui.radio_value(&mut self.bezier, 3, "Quadratic").clicked()
|
||||
&& self.degree_backup != self.bezier
|
||||
{
|
||||
self.points.clear();
|
||||
self.degree_backup = self.bezier;
|
||||
};
|
||||
if ui.radio_value(&mut self.bezier, 4, "Cubic").clicked()
|
||||
&& self.degree_backup != self.bezier
|
||||
{
|
||||
self.points.clear();
|
||||
self.degree_backup = self.bezier;
|
||||
};
|
||||
// ui.radio_value(self.bezier, 5, "Quintic");
|
||||
ui.label("Click 3 or 4 points to build a bezier curve!");
|
||||
if ui.button("Clear Painting").clicked() {
|
||||
self.points.clear();
|
||||
self.backup_points.clear();
|
||||
self.q_shapes.clear();
|
||||
self.c_shapes.clear();
|
||||
}
|
||||
})
|
||||
})
|
||||
.response
|
||||
ui.radio_value(&mut self.degree, 3, "Quadratic Bézier");
|
||||
ui.radio_value(&mut self.degree, 4, "Cubic Bézier");
|
||||
ui.label("Move the points by dragging them.");
|
||||
ui.small("Only convex curves can be accurately filled.");
|
||||
}
|
||||
|
||||
pub fn ui_content(&mut self, ui: &mut Ui) -> egui::Response {
|
||||
let (mut response, painter) =
|
||||
ui.allocate_painter(ui.available_size_before_wrap(), Sense::click());
|
||||
let (response, painter) =
|
||||
ui.allocate_painter(Vec2::new(ui.available_width(), 300.0), Sense::hover());
|
||||
|
||||
let to_screen = emath::RectTransform::from_to(
|
||||
Rect::from_min_size(Pos2::ZERO, response.rect.square_proportions()),
|
||||
Rect::from_min_size(Pos2::ZERO, response.rect.size()),
|
||||
response.rect,
|
||||
);
|
||||
let from_screen = to_screen.inverse();
|
||||
|
||||
if response.clicked() {
|
||||
if let Some(pointer_pos) = response.interact_pointer_pos() {
|
||||
let canvas_pos = from_screen * pointer_pos;
|
||||
self.points.push(canvas_pos);
|
||||
if self.points.len() >= self.bezier {
|
||||
self.backup_points = self.points.clone();
|
||||
let points = self.points.drain(..).collect::<Vec<_>>();
|
||||
match points.len() {
|
||||
3 => {
|
||||
let quadratic = QuadraticBezierShape::from_points_stroke(
|
||||
points,
|
||||
self.closed,
|
||||
self.fill,
|
||||
self.stroke,
|
||||
);
|
||||
self.q_shapes.push(quadratic);
|
||||
}
|
||||
4 => {
|
||||
let cubic = CubicBezierShape::from_points_stroke(
|
||||
points,
|
||||
self.closed,
|
||||
self.fill,
|
||||
self.stroke,
|
||||
);
|
||||
self.c_shapes.push(cubic);
|
||||
}
|
||||
_ => {
|
||||
unreachable!();
|
||||
}
|
||||
}
|
||||
}
|
||||
let control_point_radius = 8.0;
|
||||
|
||||
response.mark_changed();
|
||||
}
|
||||
}
|
||||
let mut shapes = Vec::new();
|
||||
for shape in self.q_shapes.iter() {
|
||||
shapes.push(shape.to_screen(&to_screen).into());
|
||||
if self.show_bounding_box {
|
||||
shapes.push(self.build_bounding_box(shape.bounding_rect(), &to_screen));
|
||||
}
|
||||
}
|
||||
for shape in self.c_shapes.iter() {
|
||||
shapes.push(shape.to_screen(&to_screen).into());
|
||||
if self.show_bounding_box {
|
||||
shapes.push(self.build_bounding_box(shape.bounding_rect(), &to_screen));
|
||||
}
|
||||
}
|
||||
painter.extend(shapes);
|
||||
let mut control_point_shapes = vec![];
|
||||
|
||||
if !self.points.is_empty() {
|
||||
painter.extend(build_auxiliary_line(
|
||||
&self.points,
|
||||
&to_screen,
|
||||
&self.aux_stroke,
|
||||
));
|
||||
} else if !self.backup_points.is_empty() {
|
||||
painter.extend(build_auxiliary_line(
|
||||
&self.backup_points,
|
||||
&to_screen,
|
||||
&self.aux_stroke,
|
||||
for (i, point) in self.control_points.iter_mut().enumerate().take(self.degree) {
|
||||
let size = Vec2::splat(2.0 * control_point_radius);
|
||||
|
||||
let point_in_screen = to_screen.transform_pos(*point);
|
||||
let point_rect = Rect::from_center_size(point_in_screen, size);
|
||||
let point_id = response.id.with(i);
|
||||
let point_response = ui.interact(point_rect, point_id, Sense::drag());
|
||||
|
||||
*point += point_response.drag_delta();
|
||||
*point = to_screen.from().clamp(*point);
|
||||
|
||||
let point_in_screen = to_screen.transform_pos(*point);
|
||||
let stroke = ui.style().interact(&point_response).fg_stroke;
|
||||
|
||||
control_point_shapes.push(Shape::circle_stroke(
|
||||
point_in_screen,
|
||||
control_point_radius,
|
||||
stroke,
|
||||
));
|
||||
}
|
||||
|
||||
let points_in_screen: Vec<Pos2> = self
|
||||
.control_points
|
||||
.iter()
|
||||
.take(self.degree)
|
||||
.map(|p| to_screen * *p)
|
||||
.collect();
|
||||
|
||||
match self.degree {
|
||||
3 => {
|
||||
let points = points_in_screen.clone().try_into().unwrap();
|
||||
let shape =
|
||||
QuadraticBezierShape::from_points_stroke(points, true, self.fill, self.stroke);
|
||||
painter.add(epaint::RectShape::stroke(
|
||||
shape.visual_bounding_rect(),
|
||||
0.0,
|
||||
self.bounding_box_stroke,
|
||||
));
|
||||
painter.add(shape);
|
||||
}
|
||||
4 => {
|
||||
let points = points_in_screen.clone().try_into().unwrap();
|
||||
let shape =
|
||||
CubicBezierShape::from_points_stroke(points, true, self.fill, self.stroke);
|
||||
painter.add(epaint::RectShape::stroke(
|
||||
shape.visual_bounding_rect(),
|
||||
0.0,
|
||||
self.bounding_box_stroke,
|
||||
));
|
||||
painter.add(shape);
|
||||
}
|
||||
_ => {
|
||||
unreachable!();
|
||||
}
|
||||
};
|
||||
|
||||
painter.add(PathShape::line(points_in_screen, self.aux_stroke));
|
||||
painter.extend(control_point_shapes);
|
||||
|
||||
response
|
||||
}
|
||||
|
||||
pub fn build_bounding_box(&self, bbox: Rect, to_screen: &RectTransform) -> Shape {
|
||||
let bbox = Rect {
|
||||
min: to_screen * bbox.min,
|
||||
max: to_screen * bbox.max,
|
||||
};
|
||||
let bbox_shape = epaint::RectShape::stroke(bbox, 0.0, self.bounding_box_stroke);
|
||||
bbox_shape.into()
|
||||
}
|
||||
}
|
||||
|
||||
/// An internal function to create auxiliary lines around the current bezier curve
|
||||
/// or to auxiliary lines (points) before the points meet the bezier curve requirements.
|
||||
fn build_auxiliary_line(
|
||||
points: &[Pos2],
|
||||
to_screen: &RectTransform,
|
||||
aux_stroke: &Stroke,
|
||||
) -> Vec<Shape> {
|
||||
let mut shapes = Vec::new();
|
||||
if points.len() >= 2 {
|
||||
let points: Vec<Pos2> = points.iter().map(|p| to_screen * *p).collect();
|
||||
shapes.push(egui::Shape::line(points, *aux_stroke));
|
||||
}
|
||||
for point in points.iter() {
|
||||
let center = to_screen * *point;
|
||||
let radius = aux_stroke.width * 3.0;
|
||||
let circle = CircleShape {
|
||||
center,
|
||||
radius,
|
||||
fill: aux_stroke.color,
|
||||
stroke: *aux_stroke,
|
||||
};
|
||||
|
||||
shapes.push(circle.into());
|
||||
}
|
||||
|
||||
shapes
|
||||
}
|
||||
|
||||
impl super::Demo for PaintBezier {
|
||||
fn name(&self) -> &'static str {
|
||||
"✔ Bezier Curve"
|
||||
") Bézier Curve"
|
||||
}
|
||||
|
||||
fn show(&mut self, ctx: &Context, open: &mut bool) {
|
||||
use super::View as _;
|
||||
Window::new(self.name())
|
||||
.open(open)
|
||||
.default_size(vec2(512.0, 512.0))
|
||||
.vscroll(false)
|
||||
.resizable(false)
|
||||
.default_size([300.0, 350.0])
|
||||
.show(ctx, |ui| self.ui(ui));
|
||||
}
|
||||
}
|
||||
|
||||
impl super::View for PaintBezier {
|
||||
fn ui(&mut self, ui: &mut Ui) {
|
||||
// ui.vertical_centered(|ui| {
|
||||
// ui.add(crate::__egui_github_link_file!());
|
||||
// });
|
||||
ui.vertical_centered(|ui| {
|
||||
ui.add(crate::__egui_github_link_file!());
|
||||
});
|
||||
self.ui_control(ui);
|
||||
|
||||
Frame::dark_canvas(ui.style()).show(ui, |ui| {
|
||||
|
||||
@@ -2,8 +2,9 @@ use std::f64::consts::TAU;
|
||||
|
||||
use egui::*;
|
||||
use plot::{
|
||||
Arrows, Bar, BarChart, BoxElem, BoxPlot, BoxSpread, Corner, HLine, Legend, Line, LineStyle,
|
||||
MarkerShape, Plot, PlotImage, Points, Polygon, Text, VLine, Value, Values,
|
||||
Arrows, Bar, BarChart, BoxElem, BoxPlot, BoxSpread, CoordinatesFormatter, Corner, HLine,
|
||||
Legend, Line, LineStyle, MarkerShape, Plot, PlotImage, Points, Polygon, Text, VLine, Value,
|
||||
Values,
|
||||
};
|
||||
|
||||
#[derive(PartialEq)]
|
||||
@@ -14,6 +15,7 @@ struct LineDemo {
|
||||
circle_center: Pos2,
|
||||
square: bool,
|
||||
proportional: bool,
|
||||
coordinates: bool,
|
||||
line_style: LineStyle,
|
||||
}
|
||||
|
||||
@@ -26,6 +28,7 @@ impl Default for LineDemo {
|
||||
circle_center: Pos2::new(0.0, 0.0),
|
||||
square: false,
|
||||
proportional: true,
|
||||
coordinates: true,
|
||||
line_style: LineStyle::Solid,
|
||||
}
|
||||
}
|
||||
@@ -41,6 +44,7 @@ impl LineDemo {
|
||||
square,
|
||||
proportional,
|
||||
line_style,
|
||||
coordinates,
|
||||
..
|
||||
} = self;
|
||||
|
||||
@@ -76,6 +80,8 @@ impl LineDemo {
|
||||
.on_hover_text("Always keep the viewport square.");
|
||||
ui.checkbox(proportional, "Proportional data axes")
|
||||
.on_hover_text("Tick are the same size on both axes.");
|
||||
ui.checkbox(coordinates, "Show coordinates")
|
||||
.on_hover_text("Can take a custom formatting function.");
|
||||
|
||||
ComboBox::from_label("Line style")
|
||||
.selected_text(line_style.to_string())
|
||||
@@ -151,6 +157,9 @@ impl Widget for &mut LineDemo {
|
||||
if self.proportional {
|
||||
plot = plot.data_aspect(1.0);
|
||||
}
|
||||
if self.coordinates {
|
||||
plot = plot.coordinates_formatter(Corner::LeftBottom, CoordinatesFormatter::default());
|
||||
}
|
||||
plot.show(ui, |plot_ui| {
|
||||
plot_ui.line(self.circle());
|
||||
plot_ui.line(self.sin());
|
||||
@@ -595,7 +604,7 @@ impl ChartsDemo {
|
||||
.name("Set 4")
|
||||
.stack_on(&[&chart1, &chart2, &chart3]);
|
||||
|
||||
let mut x_fmt: fn(f64) -> String = |val| {
|
||||
let mut x_fmt: fn(f64, &std::ops::RangeInclusive<f64>) -> String = |val, _range| {
|
||||
if val >= 0.0 && val <= 4.0 && is_approx_integer(val) {
|
||||
// Only label full days from 0 to 4
|
||||
format!("Day {}", val)
|
||||
@@ -605,7 +614,7 @@ impl ChartsDemo {
|
||||
}
|
||||
};
|
||||
|
||||
let mut y_fmt: fn(f64) -> String = |val| {
|
||||
let mut y_fmt: fn(f64, &std::ops::RangeInclusive<f64>) -> String = |val, _range| {
|
||||
let percent = 100.0 * val;
|
||||
|
||||
if is_approx_integer(percent) && !is_approx_zero(percent) {
|
||||
|
||||
@@ -147,7 +147,7 @@ fn huge_content_painter(ui: &mut egui::Ui) {
|
||||
#[derive(PartialEq)]
|
||||
struct ScrollTo {
|
||||
track_item: usize,
|
||||
tack_item_align: Align,
|
||||
tack_item_align: Option<Align>,
|
||||
offset: f32,
|
||||
}
|
||||
|
||||
@@ -155,7 +155,7 @@ impl Default for ScrollTo {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
track_item: 25,
|
||||
tack_item_align: Align::Center,
|
||||
tack_item_align: Some(Align::Center),
|
||||
offset: 0.0,
|
||||
}
|
||||
}
|
||||
@@ -180,13 +180,16 @@ impl super::View for ScrollTo {
|
||||
ui.horizontal(|ui| {
|
||||
ui.label("Item align:");
|
||||
track_item |= ui
|
||||
.radio_value(&mut self.tack_item_align, Align::Min, "Top")
|
||||
.radio_value(&mut self.tack_item_align, Some(Align::Min), "Top")
|
||||
.clicked();
|
||||
track_item |= ui
|
||||
.radio_value(&mut self.tack_item_align, Align::Center, "Center")
|
||||
.radio_value(&mut self.tack_item_align, Some(Align::Center), "Center")
|
||||
.clicked();
|
||||
track_item |= ui
|
||||
.radio_value(&mut self.tack_item_align, Align::Max, "Bottom")
|
||||
.radio_value(&mut self.tack_item_align, Some(Align::Max), "Bottom")
|
||||
.clicked();
|
||||
track_item |= ui
|
||||
.radio_value(&mut self.tack_item_align, None, "None (Bring into view)")
|
||||
.clicked();
|
||||
});
|
||||
|
||||
@@ -213,7 +216,7 @@ impl super::View for ScrollTo {
|
||||
let (current_scroll, max_scroll) = scroll_area
|
||||
.show(ui, |ui| {
|
||||
if scroll_top {
|
||||
ui.scroll_to_cursor(Align::TOP);
|
||||
ui.scroll_to_cursor(Some(Align::TOP));
|
||||
}
|
||||
ui.vertical(|ui| {
|
||||
for item in 1..=50 {
|
||||
@@ -228,7 +231,7 @@ impl super::View for ScrollTo {
|
||||
});
|
||||
|
||||
if scroll_bottom {
|
||||
ui.scroll_to_cursor(Align::BOTTOM);
|
||||
ui.scroll_to_cursor(Some(Align::BOTTOM));
|
||||
}
|
||||
|
||||
let margin = ui.visuals().clip_rect_margin;
|
||||
|
||||
@@ -11,6 +11,8 @@ pub struct Sliders {
|
||||
pub logarithmic: bool,
|
||||
pub clamp_to_range: bool,
|
||||
pub smart_aim: bool,
|
||||
pub step: f64,
|
||||
pub use_steps: bool,
|
||||
pub integer: bool,
|
||||
pub vertical: bool,
|
||||
pub value: f64,
|
||||
@@ -24,6 +26,8 @@ impl Default for Sliders {
|
||||
logarithmic: true,
|
||||
clamp_to_range: false,
|
||||
smart_aim: true,
|
||||
step: 10.0,
|
||||
use_steps: false,
|
||||
integer: false,
|
||||
vertical: false,
|
||||
value: 10.0,
|
||||
@@ -55,6 +59,8 @@ impl super::View for Sliders {
|
||||
logarithmic,
|
||||
clamp_to_range,
|
||||
smart_aim,
|
||||
step,
|
||||
use_steps,
|
||||
integer,
|
||||
vertical,
|
||||
value,
|
||||
@@ -79,6 +85,7 @@ impl super::View for Sliders {
|
||||
SliderOrientation::Horizontal
|
||||
};
|
||||
|
||||
let istep = if *use_steps { *step } else { 0.0 };
|
||||
if *integer {
|
||||
let mut value_i32 = *value as i32;
|
||||
ui.add(
|
||||
@@ -87,7 +94,8 @@ impl super::View for Sliders {
|
||||
.clamp_to_range(*clamp_to_range)
|
||||
.smart_aim(*smart_aim)
|
||||
.orientation(orientation)
|
||||
.text("i32 demo slider"),
|
||||
.text("i32 demo slider")
|
||||
.step_by(istep),
|
||||
);
|
||||
*value = value_i32 as f64;
|
||||
} else {
|
||||
@@ -97,7 +105,8 @@ impl super::View for Sliders {
|
||||
.clamp_to_range(*clamp_to_range)
|
||||
.smart_aim(*smart_aim)
|
||||
.orientation(orientation)
|
||||
.text("f64 demo slider"),
|
||||
.text("f64 demo slider")
|
||||
.step_by(istep),
|
||||
);
|
||||
|
||||
ui.label(
|
||||
@@ -128,6 +137,14 @@ impl super::View for Sliders {
|
||||
|
||||
ui.separator();
|
||||
|
||||
ui.checkbox(use_steps, "Use steps");
|
||||
ui.label("When enabled, the minimal value change would be restricted to a given step.");
|
||||
if *use_steps {
|
||||
ui.add(egui::DragValue::new(step).speed(1.0));
|
||||
}
|
||||
|
||||
ui.separator();
|
||||
|
||||
ui.horizontal(|ui| {
|
||||
ui.label("Slider type:");
|
||||
ui.radio_value(integer, true, "i32");
|
||||
|
||||
@@ -64,7 +64,10 @@ impl super::View for TextEdit {
|
||||
anything_selected,
|
||||
egui::Label::new("Press ctrl+T to toggle the case of selected text (cmd+T on Mac)"),
|
||||
);
|
||||
if ui.input().modifiers.command_only() && ui.input().key_pressed(egui::Key::T) {
|
||||
if ui
|
||||
.input_mut()
|
||||
.consume_key(egui::Modifiers::COMMAND, egui::Key::T)
|
||||
{
|
||||
if let Some(text_cursor_range) = output.cursor_range {
|
||||
use egui::TextBuffer as _;
|
||||
let selected_chars = text_cursor_range.as_sorted_char_range();
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
use egui_extras::RetainedImage;
|
||||
use poll_promise::Promise;
|
||||
|
||||
struct Resource {
|
||||
@@ -7,7 +8,7 @@ struct Resource {
|
||||
text: Option<String>,
|
||||
|
||||
/// If set, the response was an image.
|
||||
texture: Option<egui::TextureHandle>,
|
||||
image: Option<RetainedImage>,
|
||||
|
||||
/// If set, the response was text with some supported syntax highlighting (e.g. ".rs" or ".md").
|
||||
colored_text: Option<ColoredText>,
|
||||
@@ -17,13 +18,11 @@ impl Resource {
|
||||
fn from_response(ctx: &egui::Context, response: ehttp::Response) -> Self {
|
||||
let content_type = response.content_type().unwrap_or_default();
|
||||
let image = if content_type.starts_with("image/") {
|
||||
load_image(&response.bytes).ok()
|
||||
RetainedImage::from_image_bytes(&response.url, &response.bytes).ok()
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let texture = image.map(|image| ctx.load_texture(&response.url, image));
|
||||
|
||||
let text = response.text();
|
||||
let colored_text = text.and_then(|text| syntax_highlighting(ctx, &response, text));
|
||||
let text = text.map(|text| text.to_owned());
|
||||
@@ -31,7 +30,7 @@ impl Resource {
|
||||
Self {
|
||||
response,
|
||||
text,
|
||||
texture,
|
||||
image,
|
||||
colored_text,
|
||||
}
|
||||
}
|
||||
@@ -151,7 +150,7 @@ fn ui_resource(ui: &mut egui::Ui, resource: &Resource) {
|
||||
let Resource {
|
||||
response,
|
||||
text,
|
||||
texture,
|
||||
image,
|
||||
colored_text,
|
||||
} = resource;
|
||||
|
||||
@@ -198,10 +197,10 @@ fn ui_resource(ui: &mut egui::Ui, resource: &Resource) {
|
||||
ui.separator();
|
||||
}
|
||||
|
||||
if let Some(texture) = texture {
|
||||
let mut size = texture.size_vec2();
|
||||
if let Some(image) = image {
|
||||
let mut size = image.size_vec2();
|
||||
size *= (ui.available_width() / size.x).min(1.0);
|
||||
ui.image(texture, size);
|
||||
image.show_size(ui, size);
|
||||
} else if let Some(colored_text) = colored_text {
|
||||
colored_text.ui(ui);
|
||||
} else if let Some(text) = &text {
|
||||
@@ -270,16 +269,3 @@ impl ColoredText {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
fn load_image(image_data: &[u8]) -> Result<egui::ColorImage, image::ImageError> {
|
||||
let image = image::load_from_memory(image_data)?;
|
||||
let size = [image.width() as _, image.height() as _];
|
||||
let image_buffer = image.to_rgba8();
|
||||
let pixels = image_buffer.as_flat_samples();
|
||||
Ok(egui::ColorImage::from_rgba_unmultiplied(
|
||||
size,
|
||||
pixels.as_slice(),
|
||||
))
|
||||
}
|
||||
|
||||
@@ -172,6 +172,12 @@ impl BackendPanel {
|
||||
|
||||
show_integration_name(ui, &frame.info());
|
||||
|
||||
if let Some(web_info) = &frame.info().web_info {
|
||||
ui.collapsing("Web info (location)", |ui| {
|
||||
ui.monospace(format!("{:#?}", web_info.location));
|
||||
});
|
||||
}
|
||||
|
||||
// For instance: `egui_web` sets `pixels_per_point` every frame to force
|
||||
// egui to use the same scale as the web zoom factor.
|
||||
let integration_controls_pixels_per_point = ui.input().raw.pixels_per_point.is_some();
|
||||
|
||||
@@ -117,59 +117,24 @@ impl EasyMarkEditor {
|
||||
|
||||
fn shortcuts(ui: &Ui, code: &mut dyn TextBuffer, ccursor_range: &mut CCursorRange) -> bool {
|
||||
let mut any_change = false;
|
||||
for event in &ui.input().events {
|
||||
if let Event::Key {
|
||||
key,
|
||||
pressed: true,
|
||||
modifiers,
|
||||
} = event
|
||||
{
|
||||
if modifiers.command_only() {
|
||||
match &key {
|
||||
// toggle *bold*
|
||||
Key::B => {
|
||||
toggle_surrounding(code, ccursor_range, "*");
|
||||
any_change = true;
|
||||
}
|
||||
// toggle `code`
|
||||
Key::C => {
|
||||
toggle_surrounding(code, ccursor_range, "`");
|
||||
any_change = true;
|
||||
}
|
||||
// toggle /italics/
|
||||
Key::I => {
|
||||
toggle_surrounding(code, ccursor_range, "/");
|
||||
any_change = true;
|
||||
}
|
||||
// toggle $lowered$
|
||||
Key::L => {
|
||||
toggle_surrounding(code, ccursor_range, "$");
|
||||
any_change = true;
|
||||
}
|
||||
// toggle ^raised^
|
||||
Key::R => {
|
||||
toggle_surrounding(code, ccursor_range, "^");
|
||||
any_change = true;
|
||||
}
|
||||
// toggle ~strikethrough~
|
||||
Key::S => {
|
||||
toggle_surrounding(code, ccursor_range, "~");
|
||||
any_change = true;
|
||||
}
|
||||
// toggle _underline_
|
||||
Key::U => {
|
||||
toggle_surrounding(code, ccursor_range, "_");
|
||||
any_change = true;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
for (key, surrounding) in [
|
||||
(Key::B, "*"), // *bold*
|
||||
(Key::C, "`"), // `code`
|
||||
(Key::I, "/"), // /italics/
|
||||
(Key::L, "$"), // $subscript$
|
||||
(Key::R, "^"), // ^superscript^
|
||||
(Key::S, "~"), // ~strikethrough~
|
||||
(Key::U, "_"), // _underline_
|
||||
] {
|
||||
if ui.input_mut().consume_key(egui::Modifiers::COMMAND, key) {
|
||||
toggle_surrounding(code, ccursor_range, surrounding);
|
||||
any_change = true;
|
||||
};
|
||||
}
|
||||
any_change
|
||||
}
|
||||
|
||||
/// E.g. toggle *strong* with `toggle(&mut text, &mut cursor, "*")`
|
||||
/// E.g. toggle *strong* with `toggle_surrounding(&mut text, &mut cursor, "*")`
|
||||
fn toggle_surrounding(
|
||||
code: &mut dyn TextBuffer,
|
||||
ccursor_range: &mut CCursorRange,
|
||||
|
||||
@@ -145,10 +145,10 @@ fn test_egui_e2e() {
|
||||
|
||||
const NUM_FRAMES: usize = 5;
|
||||
for _ in 0..NUM_FRAMES {
|
||||
let (_output, shapes) = ctx.run(raw_input.clone(), |ctx| {
|
||||
let full_output = ctx.run(raw_input.clone(), |ctx| {
|
||||
demo_windows.ui(ctx);
|
||||
});
|
||||
let clipped_meshes = ctx.tessellate(shapes);
|
||||
let clipped_meshes = ctx.tessellate(full_output.shapes);
|
||||
assert!(!clipped_meshes.is_empty());
|
||||
}
|
||||
}
|
||||
@@ -164,10 +164,10 @@ fn test_egui_zero_window_size() {
|
||||
|
||||
const NUM_FRAMES: usize = 5;
|
||||
for _ in 0..NUM_FRAMES {
|
||||
let (_output, shapes) = ctx.run(raw_input.clone(), |ctx| {
|
||||
let full_output = ctx.run(raw_input.clone(), |ctx| {
|
||||
demo_windows.ui(ctx);
|
||||
});
|
||||
let clipped_meshes = ctx.tessellate(shapes);
|
||||
let clipped_meshes = ctx.tessellate(full_output.shapes);
|
||||
assert!(clipped_meshes.is_empty(), "There should be nothing to show");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -69,7 +69,7 @@ impl epi::App for WrapApp {
|
||||
|
||||
fn update(&mut self, ctx: &egui::Context, frame: &epi::Frame) {
|
||||
if let Some(web_info) = frame.info().web_info.as_ref() {
|
||||
if let Some(anchor) = web_info.web_location_hash.strip_prefix('#') {
|
||||
if let Some(anchor) = web_info.location.hash.strip_prefix('#') {
|
||||
self.selected_anchor = anchor.to_owned();
|
||||
}
|
||||
}
|
||||
@@ -108,12 +108,19 @@ impl epi::App for WrapApp {
|
||||
});
|
||||
}
|
||||
|
||||
let mut found_anchor = false;
|
||||
|
||||
for (anchor, app) in self.apps.iter_mut() {
|
||||
if anchor == self.selected_anchor || ctx.memory().everything_is_visible() {
|
||||
app.update(ctx, frame);
|
||||
found_anchor = true;
|
||||
}
|
||||
}
|
||||
|
||||
if !found_anchor {
|
||||
self.selected_anchor = "demo".into();
|
||||
}
|
||||
|
||||
self.backend_panel.end_of_frame(ctx);
|
||||
|
||||
self.ui_file_drag_and_drop(ctx);
|
||||
|
||||
Reference in New Issue
Block a user