Shape refactor (#705)
* More introspection stats about vertices/indices etc * more serde derive * #[inline] to Shape constructors * Introduce RectShape * Introduce CircleShape * Introduce PathShape * More serde derive * impl Copy for RectShape and CircleShape * Simplify some code * More serde derive * Add helpers for appending more input or output * Serde derives for RawInput * Rename Fonts::from_definitions to Fonts::new * Add Output::take * refactor EguiGlium slightly * Derive PartialEq for RawInput * Improve egui::util::History interface * tweaks * Improve History filter: add minimum length * Calculate galley bounding rect * tessellator: cull line segments and paths * tessellator: cull meshes * Fix bug in History bandwidth estimator
This commit is contained in:
parent
93c2fde1fc
commit
e7cfda4941
36 changed files with 578 additions and 313 deletions
|
@ -305,7 +305,7 @@ impl CollapsingHeader {
|
||||||
.unwrap_or_else(|| visuals.text_color());
|
.unwrap_or_else(|| visuals.text_color());
|
||||||
|
|
||||||
if ui.visuals().collapsing_header_frame || self.show_background {
|
if ui.visuals().collapsing_header_frame || self.show_background {
|
||||||
ui.painter().add(Shape::Rect {
|
ui.painter().add(epaint::RectShape {
|
||||||
rect: header_response.rect.expand(visuals.expansion),
|
rect: header_response.rect.expand(visuals.expansion),
|
||||||
corner_radius: visuals.corner_radius,
|
corner_radius: visuals.corner_radius,
|
||||||
fill: visuals.bg_fill,
|
fill: visuals.bg_fill,
|
||||||
|
|
|
@ -238,7 +238,7 @@ fn button_frame(
|
||||||
|
|
||||||
ui.painter().set(
|
ui.painter().set(
|
||||||
where_to_put_background,
|
where_to_put_background,
|
||||||
Shape::Rect {
|
epaint::RectShape {
|
||||||
rect: outer_rect.expand(visuals.expansion),
|
rect: outer_rect.expand(visuals.expansion),
|
||||||
corner_radius: visuals.corner_radius,
|
corner_radius: visuals.corner_radius,
|
||||||
fill: visuals.bg_fill,
|
fill: visuals.bg_fill,
|
||||||
|
|
|
@ -178,12 +178,12 @@ impl Frame {
|
||||||
stroke,
|
stroke,
|
||||||
} = *self;
|
} = *self;
|
||||||
|
|
||||||
let frame_shape = Shape::Rect {
|
let frame_shape = Shape::Rect(epaint::RectShape {
|
||||||
rect: outer_rect,
|
rect: outer_rect,
|
||||||
corner_radius,
|
corner_radius,
|
||||||
fill,
|
fill,
|
||||||
stroke,
|
stroke,
|
||||||
};
|
});
|
||||||
|
|
||||||
if shadow == Default::default() {
|
if shadow == Default::default() {
|
||||||
frame_shape
|
frame_shape
|
||||||
|
|
|
@ -282,12 +282,11 @@ impl Resize {
|
||||||
if self.with_stroke && corner_response.is_some() {
|
if self.with_stroke && corner_response.is_some() {
|
||||||
let rect = Rect::from_min_size(content_ui.min_rect().left_top(), state.desired_size);
|
let rect = Rect::from_min_size(content_ui.min_rect().left_top(), state.desired_size);
|
||||||
let rect = rect.expand(2.0); // breathing room for content
|
let rect = rect.expand(2.0); // breathing room for content
|
||||||
ui.painter().add(epaint::Shape::Rect {
|
ui.painter().add(Shape::rect_stroke(
|
||||||
rect,
|
rect,
|
||||||
corner_radius: 3.0,
|
3.0,
|
||||||
fill: Default::default(),
|
ui.visuals().widgets.noninteractive.bg_stroke,
|
||||||
stroke: ui.visuals().widgets.noninteractive.bg_stroke,
|
));
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(corner_response) = corner_response {
|
if let Some(corner_response) = corner_response {
|
||||||
|
|
|
@ -569,7 +569,7 @@ impl Context {
|
||||||
};
|
};
|
||||||
|
|
||||||
if self.fonts.is_none() || new_font_definitions.is_some() || pixels_per_point_changed {
|
if self.fonts.is_none() || new_font_definitions.is_some() || pixels_per_point_changed {
|
||||||
self.fonts = Some(Arc::new(Fonts::from_definitions(
|
self.fonts = Some(Arc::new(Fonts::new(
|
||||||
pixels_per_point,
|
pixels_per_point,
|
||||||
new_font_definitions.unwrap_or_else(|| {
|
new_font_definitions.unwrap_or_else(|| {
|
||||||
self.fonts
|
self.fonts
|
||||||
|
|
|
@ -10,7 +10,8 @@ use crate::emath::*;
|
||||||
/// [`crate::Context::wants_pointer_input`] and [`crate::Context::wants_keyboard_input`].
|
/// [`crate::Context::wants_pointer_input`] and [`crate::Context::wants_keyboard_input`].
|
||||||
///
|
///
|
||||||
/// All coordinates are in points (logical pixels) with origin (0, 0) in the top left corner.
|
/// All coordinates are in points (logical pixels) with origin (0, 0) in the top left corner.
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
|
#[cfg_attr(feature = "persistence", derive(serde::Deserialize, serde::Serialize))]
|
||||||
pub struct RawInput {
|
pub struct RawInput {
|
||||||
/// How many points (logical pixels) the user scrolled
|
/// How many points (logical pixels) the user scrolled
|
||||||
pub scroll_delta: Vec2,
|
pub scroll_delta: Vec2,
|
||||||
|
@ -100,10 +101,38 @@ impl RawInput {
|
||||||
dropped_files: std::mem::take(&mut self.dropped_files),
|
dropped_files: std::mem::take(&mut self.dropped_files),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Add on new input.
|
||||||
|
pub fn append(&mut self, newer: Self) {
|
||||||
|
let Self {
|
||||||
|
scroll_delta,
|
||||||
|
zoom_delta,
|
||||||
|
screen_rect,
|
||||||
|
pixels_per_point,
|
||||||
|
time,
|
||||||
|
predicted_dt,
|
||||||
|
modifiers,
|
||||||
|
mut events,
|
||||||
|
mut hovered_files,
|
||||||
|
mut dropped_files,
|
||||||
|
} = newer;
|
||||||
|
|
||||||
|
self.scroll_delta += scroll_delta;
|
||||||
|
self.zoom_delta *= zoom_delta;
|
||||||
|
self.screen_rect = screen_rect.or(self.screen_rect);
|
||||||
|
self.pixels_per_point = pixels_per_point.or(self.pixels_per_point);
|
||||||
|
self.time = time; // use latest time
|
||||||
|
self.predicted_dt = predicted_dt; // use latest dt
|
||||||
|
self.modifiers = modifiers; // use latest
|
||||||
|
self.events.append(&mut events);
|
||||||
|
self.hovered_files.append(&mut hovered_files);
|
||||||
|
self.dropped_files.append(&mut dropped_files);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A file about to be dropped into egui.
|
/// A file about to be dropped into egui.
|
||||||
#[derive(Clone, Debug, Default)]
|
#[derive(Clone, Debug, Default, PartialEq)]
|
||||||
|
#[cfg_attr(feature = "persistence", derive(serde::Deserialize, serde::Serialize))]
|
||||||
pub struct HoveredFile {
|
pub struct HoveredFile {
|
||||||
/// Set by the `egui_glium` backend.
|
/// Set by the `egui_glium` backend.
|
||||||
pub path: Option<std::path::PathBuf>,
|
pub path: Option<std::path::PathBuf>,
|
||||||
|
@ -112,7 +141,8 @@ pub struct HoveredFile {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A file dropped into egui.
|
/// A file dropped into egui.
|
||||||
#[derive(Clone, Debug, Default)]
|
#[derive(Clone, Debug, Default, PartialEq)]
|
||||||
|
#[cfg_attr(feature = "persistence", derive(serde::Deserialize, serde::Serialize))]
|
||||||
pub struct DroppedFile {
|
pub struct DroppedFile {
|
||||||
/// Set by the `egui_glium` backend.
|
/// Set by the `egui_glium` backend.
|
||||||
pub path: Option<std::path::PathBuf>,
|
pub path: Option<std::path::PathBuf>,
|
||||||
|
@ -128,6 +158,7 @@ pub struct DroppedFile {
|
||||||
///
|
///
|
||||||
/// This only covers events that egui cares about.
|
/// This only covers events that egui cares about.
|
||||||
#[derive(Clone, Debug, PartialEq)]
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
|
#[cfg_attr(feature = "persistence", derive(serde::Deserialize, serde::Serialize))]
|
||||||
pub enum Event {
|
pub enum Event {
|
||||||
/// The integration detected a "copy" event (e.g. Cmd+C).
|
/// The integration detected a "copy" event (e.g. Cmd+C).
|
||||||
Copy,
|
Copy,
|
||||||
|
@ -186,6 +217,7 @@ pub enum Event {
|
||||||
|
|
||||||
/// Mouse button (or similar for touch input)
|
/// Mouse button (or similar for touch input)
|
||||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||||
|
#[cfg_attr(feature = "persistence", derive(serde::Deserialize, serde::Serialize))]
|
||||||
pub enum PointerButton {
|
pub enum PointerButton {
|
||||||
/// The primary mouse button is usually the left one.
|
/// The primary mouse button is usually the left one.
|
||||||
Primary = 0,
|
Primary = 0,
|
||||||
|
@ -201,6 +233,7 @@ pub const NUM_POINTER_BUTTONS: usize = 3;
|
||||||
|
|
||||||
/// State of the modifier keys. These must be fed to egui.
|
/// State of the modifier keys. These must be fed to egui.
|
||||||
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
|
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
|
||||||
|
#[cfg_attr(feature = "persistence", derive(serde::Deserialize, serde::Serialize))]
|
||||||
pub struct Modifiers {
|
pub struct Modifiers {
|
||||||
/// Either of the alt keys are down (option ⌥ on Mac).
|
/// Either of the alt keys are down (option ⌥ on Mac).
|
||||||
pub alt: bool,
|
pub alt: bool,
|
||||||
|
@ -350,16 +383,19 @@ impl RawInput {
|
||||||
|
|
||||||
/// this is a `u64` as values of this kind can always be obtained by hashing
|
/// this is a `u64` as values of this kind can always be obtained by hashing
|
||||||
#[derive(Clone, Copy, Debug, Eq, PartialEq, PartialOrd, Ord)]
|
#[derive(Clone, Copy, Debug, Eq, PartialEq, PartialOrd, Ord)]
|
||||||
|
#[cfg_attr(feature = "persistence", derive(serde::Deserialize, serde::Serialize))]
|
||||||
pub struct TouchDeviceId(pub u64);
|
pub struct TouchDeviceId(pub u64);
|
||||||
|
|
||||||
/// Unique identification of a touch occurrence (finger or pen or ...).
|
/// Unique identification of a touch occurrence (finger or pen or ...).
|
||||||
/// A Touch ID is valid until the finger is lifted.
|
/// A Touch ID is valid until the finger is lifted.
|
||||||
/// A new ID is used for the next touch.
|
/// A new ID is used for the next touch.
|
||||||
#[derive(Clone, Copy, Debug, Eq, PartialEq, PartialOrd, Ord)]
|
#[derive(Clone, Copy, Debug, Eq, PartialEq, PartialOrd, Ord)]
|
||||||
|
#[cfg_attr(feature = "persistence", derive(serde::Deserialize, serde::Serialize))]
|
||||||
pub struct TouchId(pub u64);
|
pub struct TouchId(pub u64);
|
||||||
|
|
||||||
/// In what phase a touch event is in.
|
/// In what phase a touch event is in.
|
||||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||||
|
#[cfg_attr(feature = "persistence", derive(serde::Deserialize, serde::Serialize))]
|
||||||
pub enum TouchPhase {
|
pub enum TouchPhase {
|
||||||
/// User just placed a touch point on the touch surface
|
/// User just placed a touch point on the touch surface
|
||||||
Start,
|
Start,
|
||||||
|
|
|
@ -5,6 +5,7 @@ use crate::WidgetType;
|
||||||
/// What egui emits each frame.
|
/// What egui emits each frame.
|
||||||
/// The backend should use this.
|
/// The backend should use this.
|
||||||
#[derive(Clone, Default, PartialEq)]
|
#[derive(Clone, Default, PartialEq)]
|
||||||
|
#[cfg_attr(feature = "persistence", derive(serde::Deserialize, serde::Serialize))]
|
||||||
pub struct Output {
|
pub struct Output {
|
||||||
/// Set the cursor to this icon.
|
/// Set the cursor to this icon.
|
||||||
pub cursor_icon: CursorIcon,
|
pub cursor_icon: CursorIcon,
|
||||||
|
@ -12,7 +13,9 @@ pub struct Output {
|
||||||
/// If set, open this url.
|
/// If set, open this url.
|
||||||
pub open_url: Option<OpenUrl>,
|
pub open_url: Option<OpenUrl>,
|
||||||
|
|
||||||
/// Response to [`crate::Event::Copy`] or [`crate::Event::Cut`]. Ignore if empty.
|
/// If set, put this text in the system clipboard. Ignore if empty.
|
||||||
|
///
|
||||||
|
/// This is often a response to [`crate::Event::Copy`] or [`crate::Event::Cut`].
|
||||||
pub copied_text: String,
|
pub copied_text: String,
|
||||||
|
|
||||||
/// If `true`, egui is requesting immediate repaint (i.e. on the next frame).
|
/// If `true`, egui is requesting immediate repaint (i.e. on the next frame).
|
||||||
|
@ -53,9 +56,40 @@ impl Output {
|
||||||
}
|
}
|
||||||
Default::default()
|
Default::default()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Add on new output.
|
||||||
|
pub fn append(&mut self, newer: Self) {
|
||||||
|
let Self {
|
||||||
|
cursor_icon,
|
||||||
|
open_url,
|
||||||
|
copied_text,
|
||||||
|
needs_repaint,
|
||||||
|
mut events,
|
||||||
|
text_cursor_pos,
|
||||||
|
} = newer;
|
||||||
|
|
||||||
|
self.cursor_icon = cursor_icon;
|
||||||
|
if open_url.is_some() {
|
||||||
|
self.open_url = open_url;
|
||||||
|
}
|
||||||
|
if !copied_text.is_empty() {
|
||||||
|
self.copied_text = copied_text;
|
||||||
|
}
|
||||||
|
self.needs_repaint = needs_repaint; // if the last frame doesn't need a repaint, then we don't need to repaint
|
||||||
|
self.events.append(&mut events);
|
||||||
|
self.text_cursor_pos = text_cursor_pos.or(self.text_cursor_pos);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Take everything ephemeral (everything except `cursor_icon` currently)
|
||||||
|
pub fn take(&mut self) -> Self {
|
||||||
|
let taken = std::mem::take(self);
|
||||||
|
self.cursor_icon = taken.cursor_icon; // eveything else is ephemeral
|
||||||
|
taken
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, PartialEq)]
|
#[derive(Clone, PartialEq)]
|
||||||
|
#[cfg_attr(feature = "persistence", derive(serde::Deserialize, serde::Serialize))]
|
||||||
pub struct OpenUrl {
|
pub struct OpenUrl {
|
||||||
pub url: String,
|
pub url: String,
|
||||||
/// If `true`, open the url in a new tab.
|
/// If `true`, open the url in a new tab.
|
||||||
|
@ -88,6 +122,7 @@ impl OpenUrl {
|
||||||
///
|
///
|
||||||
/// Loosely based on <https://developer.mozilla.org/en-US/docs/Web/CSS/cursor>.
|
/// Loosely based on <https://developer.mozilla.org/en-US/docs/Web/CSS/cursor>.
|
||||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||||
|
#[cfg_attr(feature = "persistence", derive(serde::Deserialize, serde::Serialize))]
|
||||||
pub enum CursorIcon {
|
pub enum CursorIcon {
|
||||||
/// Normal cursor icon, whatever that is.
|
/// Normal cursor icon, whatever that is.
|
||||||
Default,
|
Default,
|
||||||
|
@ -209,6 +244,7 @@ impl Default for CursorIcon {
|
||||||
///
|
///
|
||||||
/// In particular, these events may be useful for accessability, i.e. for screen readers.
|
/// In particular, these events may be useful for accessability, i.e. for screen readers.
|
||||||
#[derive(Clone, PartialEq)]
|
#[derive(Clone, PartialEq)]
|
||||||
|
#[cfg_attr(feature = "persistence", derive(serde::Deserialize, serde::Serialize))]
|
||||||
pub enum OutputEvent {
|
pub enum OutputEvent {
|
||||||
// A widget was clicked.
|
// A widget was clicked.
|
||||||
Clicked(WidgetInfo),
|
Clicked(WidgetInfo),
|
||||||
|
@ -236,6 +272,7 @@ impl std::fmt::Debug for OutputEvent {
|
||||||
|
|
||||||
/// Describes a widget such as a [`crate::Button`] or a [`crate::TextEdit`].
|
/// Describes a widget such as a [`crate::Button`] or a [`crate::TextEdit`].
|
||||||
#[derive(Clone, PartialEq)]
|
#[derive(Clone, PartialEq)]
|
||||||
|
#[cfg_attr(feature = "persistence", derive(serde::Deserialize, serde::Serialize))]
|
||||||
pub struct WidgetInfo {
|
pub struct WidgetInfo {
|
||||||
/// The type of widget this is.
|
/// The type of widget this is.
|
||||||
pub typ: WidgetType,
|
pub typ: WidgetType,
|
||||||
|
|
|
@ -378,7 +378,7 @@ impl Default for PointerState {
|
||||||
interact_pos: None,
|
interact_pos: None,
|
||||||
delta: Vec2::ZERO,
|
delta: Vec2::ZERO,
|
||||||
velocity: Vec2::ZERO,
|
velocity: Vec2::ZERO,
|
||||||
pos_history: History::new(1000, 0.1),
|
pos_history: History::new(0..1000, 0.1),
|
||||||
down: Default::default(),
|
down: Default::default(),
|
||||||
press_origin: None,
|
press_origin: None,
|
||||||
press_start_time: None,
|
press_start_time: None,
|
||||||
|
|
|
@ -89,6 +89,8 @@ impl Widget for &epaint::stats::PaintStats {
|
||||||
shape_path,
|
shape_path,
|
||||||
shape_mesh,
|
shape_mesh,
|
||||||
shape_vec,
|
shape_vec,
|
||||||
|
text_shape_vertices,
|
||||||
|
text_shape_indices,
|
||||||
clipped_meshes,
|
clipped_meshes,
|
||||||
vertices,
|
vertices,
|
||||||
indices,
|
indices,
|
||||||
|
@ -96,13 +98,19 @@ impl Widget for &epaint::stats::PaintStats {
|
||||||
|
|
||||||
ui.label("Intermediate:");
|
ui.label("Intermediate:");
|
||||||
label(ui, shapes, "shapes").on_hover_text("Boxes, circles, etc");
|
label(ui, shapes, "shapes").on_hover_text("Boxes, circles, etc");
|
||||||
label(ui, shape_text, "text");
|
label(ui, shape_text, "text (mostly cached)");
|
||||||
label(ui, shape_path, "paths");
|
label(ui, shape_path, "paths");
|
||||||
label(ui, shape_mesh, "nested meshes");
|
label(ui, shape_mesh, "nested meshes");
|
||||||
label(ui, shape_vec, "nested shapes");
|
label(ui, shape_vec, "nested shapes");
|
||||||
ui.add_space(10.0);
|
ui.add_space(10.0);
|
||||||
|
|
||||||
ui.label("Tessellated:");
|
ui.label("Text shapes:");
|
||||||
|
label(ui, text_shape_vertices, "vertices");
|
||||||
|
label(ui, text_shape_indices, "indices")
|
||||||
|
.on_hover_text("Three 32-bit indices per triangles");
|
||||||
|
ui.add_space(10.0);
|
||||||
|
|
||||||
|
ui.label("Tessellated (and culled):");
|
||||||
label(ui, clipped_meshes, "clipped_meshes")
|
label(ui, clipped_meshes, "clipped_meshes")
|
||||||
.on_hover_text("Number of separate clip rectangles");
|
.on_hover_text("Number of separate clip rectangles");
|
||||||
label(ui, vertices, "vertices");
|
label(ui, vertices, "vertices");
|
||||||
|
|
|
@ -397,7 +397,7 @@ pub use {
|
||||||
};
|
};
|
||||||
|
|
||||||
pub mod text {
|
pub mod text {
|
||||||
pub use epaint::text::{Galley, LayoutJob, LayoutSection, TextFormat, TAB_SIZE};
|
pub use epaint::text::{Fonts, Galley, LayoutJob, LayoutSection, TextFormat, TAB_SIZE};
|
||||||
}
|
}
|
||||||
|
|
||||||
// ----------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------
|
||||||
|
@ -527,6 +527,7 @@ pub mod special_emojis {
|
||||||
|
|
||||||
/// The different types of built-in widgets in egui
|
/// The different types of built-in widgets in egui
|
||||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||||
|
#[cfg_attr(feature = "persistence", derive(serde::Deserialize, serde::Serialize))]
|
||||||
pub enum WidgetType {
|
pub enum WidgetType {
|
||||||
Label, // TODO: emit Label events
|
Label, // TODO: emit Label events
|
||||||
Hyperlink,
|
Hyperlink,
|
||||||
|
|
|
@ -6,7 +6,7 @@ use crate::{
|
||||||
use epaint::{
|
use epaint::{
|
||||||
mutex::Mutex,
|
mutex::Mutex,
|
||||||
text::{Fonts, Galley, TextStyle},
|
text::{Fonts, Galley, TextStyle},
|
||||||
Shape, Stroke, TextShape,
|
CircleShape, RectShape, Shape, Stroke, TextShape,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Helper to paint shapes and text to a specific region on a specific layer.
|
/// Helper to paint shapes and text to a specific region on a specific layer.
|
||||||
|
@ -183,10 +183,11 @@ impl Painter {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Modify an existing [`Shape`].
|
/// Modify an existing [`Shape`].
|
||||||
pub fn set(&self, idx: ShapeIdx, mut shape: Shape) {
|
pub fn set(&self, idx: ShapeIdx, shape: impl Into<Shape>) {
|
||||||
if self.fade_to_color == Some(Color32::TRANSPARENT) {
|
if self.fade_to_color == Some(Color32::TRANSPARENT) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
let mut shape = shape.into();
|
||||||
self.transform_shape(&mut shape);
|
self.transform_shape(&mut shape);
|
||||||
self.paint_list.lock().set(idx, self.clip_rect, shape)
|
self.paint_list.lock().set(idx, self.clip_rect, shape)
|
||||||
}
|
}
|
||||||
|
@ -223,13 +224,11 @@ impl Painter {
|
||||||
let galley = self.layout_no_wrap(text.to_string(), TextStyle::Monospace, color);
|
let galley = self.layout_no_wrap(text.to_string(), TextStyle::Monospace, color);
|
||||||
let rect = anchor.anchor_rect(Rect::from_min_size(pos, galley.size()));
|
let rect = anchor.anchor_rect(Rect::from_min_size(pos, galley.size()));
|
||||||
let frame_rect = rect.expand(2.0);
|
let frame_rect = rect.expand(2.0);
|
||||||
self.add(Shape::Rect {
|
self.add(Shape::rect_filled(
|
||||||
rect: frame_rect,
|
frame_rect,
|
||||||
corner_radius: 0.0,
|
0.0,
|
||||||
fill: Color32::from_black_alpha(240),
|
Color32::from_black_alpha(240),
|
||||||
// stroke: Stroke::new(1.0, color),
|
));
|
||||||
stroke: Default::default(),
|
|
||||||
});
|
|
||||||
self.galley(rect.min, galley);
|
self.galley(rect.min, galley);
|
||||||
frame_rect
|
frame_rect
|
||||||
}
|
}
|
||||||
|
@ -251,7 +250,7 @@ impl Painter {
|
||||||
fill_color: impl Into<Color32>,
|
fill_color: impl Into<Color32>,
|
||||||
stroke: impl Into<Stroke>,
|
stroke: impl Into<Stroke>,
|
||||||
) {
|
) {
|
||||||
self.add(Shape::Circle {
|
self.add(CircleShape {
|
||||||
center,
|
center,
|
||||||
radius,
|
radius,
|
||||||
fill: fill_color.into(),
|
fill: fill_color.into(),
|
||||||
|
@ -260,7 +259,7 @@ impl Painter {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn circle_filled(&self, center: Pos2, radius: f32, fill_color: impl Into<Color32>) {
|
pub fn circle_filled(&self, center: Pos2, radius: f32, fill_color: impl Into<Color32>) {
|
||||||
self.add(Shape::Circle {
|
self.add(CircleShape {
|
||||||
center,
|
center,
|
||||||
radius,
|
radius,
|
||||||
fill: fill_color.into(),
|
fill: fill_color.into(),
|
||||||
|
@ -269,7 +268,7 @@ impl Painter {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn circle_stroke(&self, center: Pos2, radius: f32, stroke: impl Into<Stroke>) {
|
pub fn circle_stroke(&self, center: Pos2, radius: f32, stroke: impl Into<Stroke>) {
|
||||||
self.add(Shape::Circle {
|
self.add(CircleShape {
|
||||||
center,
|
center,
|
||||||
radius,
|
radius,
|
||||||
fill: Default::default(),
|
fill: Default::default(),
|
||||||
|
@ -284,7 +283,7 @@ impl Painter {
|
||||||
fill_color: impl Into<Color32>,
|
fill_color: impl Into<Color32>,
|
||||||
stroke: impl Into<Stroke>,
|
stroke: impl Into<Stroke>,
|
||||||
) {
|
) {
|
||||||
self.add(Shape::Rect {
|
self.add(RectShape {
|
||||||
rect,
|
rect,
|
||||||
corner_radius,
|
corner_radius,
|
||||||
fill: fill_color.into(),
|
fill: fill_color.into(),
|
||||||
|
@ -293,7 +292,7 @@ impl Painter {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn rect_filled(&self, rect: Rect, corner_radius: f32, fill_color: impl Into<Color32>) {
|
pub fn rect_filled(&self, rect: Rect, corner_radius: f32, fill_color: impl Into<Color32>) {
|
||||||
self.add(Shape::Rect {
|
self.add(RectShape {
|
||||||
rect,
|
rect,
|
||||||
corner_radius,
|
corner_radius,
|
||||||
fill: fill_color.into(),
|
fill: fill_color.into(),
|
||||||
|
@ -302,7 +301,7 @@ impl Painter {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn rect_stroke(&self, rect: Rect, corner_radius: f32, stroke: impl Into<Stroke>) {
|
pub fn rect_stroke(&self, rect: Rect, corner_radius: f32, stroke: impl Into<Stroke>) {
|
||||||
self.add(Shape::Rect {
|
self.add(RectShape {
|
||||||
rect,
|
rect,
|
||||||
corner_radius,
|
corner_radius,
|
||||||
fill: Default::default(),
|
fill: Default::default(),
|
||||||
|
|
|
@ -2,11 +2,13 @@ use std::collections::VecDeque;
|
||||||
|
|
||||||
/// This struct tracks recent values of some time series.
|
/// This struct tracks recent values of some time series.
|
||||||
///
|
///
|
||||||
/// One use is to show a log of recent events,
|
/// It can be used as a smoothing filter for e.g. latency, fps etc,
|
||||||
/// or show a graph over recent events.
|
/// or to show a log or graph of recent events.
|
||||||
///
|
///
|
||||||
/// It has both a maximum length and a maximum storage time.
|
/// It has a minimum and maximum length, as well as a maximum storage time.
|
||||||
/// Elements are dropped when either max length or max age is reached.
|
/// * The minimum length is to ensure you have enough data for an estimate.
|
||||||
|
/// * The maximum length is to make sure the history doesn't take up too much space.
|
||||||
|
/// * The maximum age is to make sure the estimate isn't outdated.
|
||||||
///
|
///
|
||||||
/// Time difference between values can be zero, but never negative.
|
/// Time difference between values can be zero, but never negative.
|
||||||
///
|
///
|
||||||
|
@ -15,11 +17,15 @@ use std::collections::VecDeque;
|
||||||
/// All times are in seconds.
|
/// All times are in seconds.
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct History<T> {
|
pub struct History<T> {
|
||||||
/// In elements, i.e. of `values.len()`
|
/// In elements, i.e. of `values.len()`.
|
||||||
|
/// The length is initially zero, but once past `min_len` will not shrink below it.
|
||||||
|
min_len: usize,
|
||||||
|
|
||||||
|
/// In elements, i.e. of `values.len()`.
|
||||||
max_len: usize,
|
max_len: usize,
|
||||||
|
|
||||||
/// In seconds
|
/// In seconds.
|
||||||
max_age: f64, // TODO: f32
|
max_age: f32,
|
||||||
|
|
||||||
/// Total number of elements seen ever
|
/// Total number of elements seen ever
|
||||||
total_count: u64,
|
total_count: u64,
|
||||||
|
@ -33,13 +39,22 @@ impl<T> History<T>
|
||||||
where
|
where
|
||||||
T: Copy,
|
T: Copy,
|
||||||
{
|
{
|
||||||
pub fn new(max_len: usize, max_age: f64) -> Self {
|
/// Example:
|
||||||
Self::from_max_len_age(max_len, max_age)
|
/// ```
|
||||||
}
|
/// # use egui::util::History;
|
||||||
|
/// # fn now() -> f64 { 0.0 }
|
||||||
pub fn from_max_len_age(max_len: usize, max_age: f64) -> Self {
|
/// // Drop events that are older than one second,
|
||||||
|
/// // as long we keep at least two events. Never keep more than a hundred events.
|
||||||
|
/// let mut history = History::new(2..100, 1.0);
|
||||||
|
/// assert_eq!(history.average(), None);
|
||||||
|
/// history.add(now(), 40.0_f32);
|
||||||
|
/// history.add(now(), 44.0_f32);
|
||||||
|
/// assert_eq!(history.average(), Some(42.0));
|
||||||
|
/// ```
|
||||||
|
pub fn new(length_range: std::ops::Range<usize>, max_age: f32) -> Self {
|
||||||
Self {
|
Self {
|
||||||
max_len,
|
min_len: length_range.start,
|
||||||
|
max_len: length_range.end,
|
||||||
max_age,
|
max_age,
|
||||||
total_count: 0,
|
total_count: 0,
|
||||||
values: Default::default(),
|
values: Default::default(),
|
||||||
|
@ -51,7 +66,7 @@ where
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn max_age(&self) -> f32 {
|
pub fn max_age(&self) -> f32 {
|
||||||
self.max_age as f32
|
self.max_age
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn is_empty(&self) -> bool {
|
pub fn is_empty(&self) -> bool {
|
||||||
|
@ -125,17 +140,26 @@ where
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Mean number of events per second.
|
||||||
|
pub fn rate(&self) -> Option<f32> {
|
||||||
|
self.mean_time_interval().map(|time| 1.0 / time)
|
||||||
|
}
|
||||||
|
|
||||||
/// Remove samples that are too old
|
/// Remove samples that are too old
|
||||||
pub fn flush(&mut self, now: f64) {
|
pub fn flush(&mut self, now: f64) {
|
||||||
while self.values.len() > self.max_len {
|
while self.values.len() > self.max_len {
|
||||||
self.values.pop_front();
|
self.values.pop_front();
|
||||||
}
|
}
|
||||||
while let Some((front_time, _)) = self.values.front() {
|
while self.values.len() > self.min_len {
|
||||||
if *front_time < now - self.max_age {
|
if let Some((front_time, _)) = self.values.front() {
|
||||||
|
if *front_time < now - (self.max_age as f64) {
|
||||||
self.values.pop_front();
|
self.values.pop_front();
|
||||||
} else {
|
} else {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -160,6 +184,21 @@ where
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<T> History<T>
|
||||||
|
where
|
||||||
|
T: Copy,
|
||||||
|
T: std::iter::Sum,
|
||||||
|
T: std::ops::Div<f32, Output = T>,
|
||||||
|
T: std::ops::Mul<f32, Output = T>,
|
||||||
|
{
|
||||||
|
/// Average times rate.
|
||||||
|
/// If you are keeping track of individual sizes of things (e.g. bytes),
|
||||||
|
/// this will estimate the bandwidth (bytes per second).
|
||||||
|
pub fn bandwidth(&self) -> Option<T> {
|
||||||
|
Some(self.average()? * self.rate()?)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl<T, Vel> History<T>
|
impl<T, Vel> History<T>
|
||||||
where
|
where
|
||||||
T: Copy,
|
T: Copy,
|
||||||
|
|
|
@ -309,7 +309,7 @@ impl<'a> Widget for Checkbox<'a> {
|
||||||
rect.center().y - 0.5 * galley.size().y,
|
rect.center().y - 0.5 * galley.size().y,
|
||||||
);
|
);
|
||||||
let (small_icon_rect, big_icon_rect) = ui.spacing().icon_rectangles(rect);
|
let (small_icon_rect, big_icon_rect) = ui.spacing().icon_rectangles(rect);
|
||||||
ui.painter().add(Shape::Rect {
|
ui.painter().add(epaint::RectShape {
|
||||||
rect: big_icon_rect.expand(visuals.expansion),
|
rect: big_icon_rect.expand(visuals.expansion),
|
||||||
corner_radius: visuals.corner_radius,
|
corner_radius: visuals.corner_radius,
|
||||||
fill: visuals.bg_fill,
|
fill: visuals.bg_fill,
|
||||||
|
@ -433,7 +433,7 @@ impl Widget for RadioButton {
|
||||||
|
|
||||||
let painter = ui.painter();
|
let painter = ui.painter();
|
||||||
|
|
||||||
painter.add(Shape::Circle {
|
painter.add(epaint::CircleShape {
|
||||||
center: big_icon_rect.center(),
|
center: big_icon_rect.center(),
|
||||||
radius: big_icon_rect.width() / 2.0 + visuals.expansion,
|
radius: big_icon_rect.width() / 2.0 + visuals.expansion,
|
||||||
fill: visuals.bg_fill,
|
fill: visuals.bg_fill,
|
||||||
|
@ -441,7 +441,7 @@ impl Widget for RadioButton {
|
||||||
});
|
});
|
||||||
|
|
||||||
if checked {
|
if checked {
|
||||||
painter.add(Shape::Circle {
|
painter.add(epaint::CircleShape {
|
||||||
center: small_icon_rect.center(),
|
center: small_icon_rect.center(),
|
||||||
radius: small_icon_rect.width() / 3.0,
|
radius: small_icon_rect.width() / 3.0,
|
||||||
fill: visuals.fg_stroke.color, // Intentional to use stroke and not fill
|
fill: visuals.fg_stroke.color, // Intentional to use stroke and not fill
|
||||||
|
|
|
@ -57,7 +57,7 @@ fn show_hsva(ui: &mut Ui, color: Hsva, desired_size: Vec2) -> Response {
|
||||||
ui.painter().rect_filled(left, 0.0, color);
|
ui.painter().rect_filled(left, 0.0, color);
|
||||||
ui.painter().rect_filled(right, 0.0, color.to_opaque());
|
ui.painter().rect_filled(right, 0.0, color.to_opaque());
|
||||||
} else {
|
} else {
|
||||||
ui.painter().add(Shape::Rect {
|
ui.painter().add(RectShape {
|
||||||
rect,
|
rect,
|
||||||
corner_radius: 2.0,
|
corner_radius: 2.0,
|
||||||
fill: color.into(),
|
fill: color.into(),
|
||||||
|
@ -190,7 +190,7 @@ fn color_slider_2d(
|
||||||
let x = lerp(rect.left()..=rect.right(), *x_value);
|
let x = lerp(rect.left()..=rect.right(), *x_value);
|
||||||
let y = lerp(rect.bottom()..=rect.top(), *y_value);
|
let y = lerp(rect.bottom()..=rect.top(), *y_value);
|
||||||
let picked_color = color_at(*x_value, *y_value);
|
let picked_color = color_at(*x_value, *y_value);
|
||||||
ui.painter().add(Shape::Circle {
|
ui.painter().add(epaint::CircleShape {
|
||||||
center: pos2(x, y),
|
center: pos2(x, y),
|
||||||
radius: rect.width() / 12.0,
|
radius: rect.width() / 12.0,
|
||||||
fill: picked_color,
|
fill: picked_color,
|
||||||
|
|
|
@ -800,12 +800,7 @@ impl PlotItem for Polygon {
|
||||||
|
|
||||||
let fill = Rgba::from(stroke.color).to_opaque().multiply(fill_alpha);
|
let fill = Rgba::from(stroke.color).to_opaque().multiply(fill_alpha);
|
||||||
|
|
||||||
let shape = Shape::Path {
|
let shape = Shape::convex_polygon(values_tf.clone(), fill, Stroke::none());
|
||||||
points: values_tf.clone(),
|
|
||||||
closed: true,
|
|
||||||
fill: fill.into(),
|
|
||||||
stroke: Stroke::none(),
|
|
||||||
};
|
|
||||||
shapes.push(shape);
|
shapes.push(shape);
|
||||||
values_tf.push(*values_tf.first().unwrap());
|
values_tf.push(*values_tf.first().unwrap());
|
||||||
style.style_line(values_tf, *stroke, *highlight, shapes);
|
style.style_line(values_tf, *stroke, *highlight, shapes);
|
||||||
|
@ -1081,21 +1076,16 @@ impl PlotItem for Points {
|
||||||
|
|
||||||
match shape {
|
match shape {
|
||||||
MarkerShape::Circle => {
|
MarkerShape::Circle => {
|
||||||
shapes.push(Shape::Circle {
|
shapes.push(Shape::Circle(epaint::CircleShape {
|
||||||
center,
|
center,
|
||||||
radius,
|
radius,
|
||||||
fill,
|
fill,
|
||||||
stroke,
|
stroke,
|
||||||
});
|
}));
|
||||||
}
|
}
|
||||||
MarkerShape::Diamond => {
|
MarkerShape::Diamond => {
|
||||||
let points = vec![tf(1.0, 0.0), tf(0.0, -1.0), tf(-1.0, 0.0), tf(0.0, 1.0)];
|
let points = vec![tf(1.0, 0.0), tf(0.0, -1.0), tf(-1.0, 0.0), tf(0.0, 1.0)];
|
||||||
shapes.push(Shape::Path {
|
shapes.push(Shape::convex_polygon(points, fill, stroke));
|
||||||
points,
|
|
||||||
closed: true,
|
|
||||||
fill,
|
|
||||||
stroke,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
MarkerShape::Square => {
|
MarkerShape::Square => {
|
||||||
let points = vec![
|
let points = vec![
|
||||||
|
@ -1104,12 +1094,7 @@ impl PlotItem for Points {
|
||||||
tf(-frac_1_sqrt_2, -frac_1_sqrt_2),
|
tf(-frac_1_sqrt_2, -frac_1_sqrt_2),
|
||||||
tf(-frac_1_sqrt_2, frac_1_sqrt_2),
|
tf(-frac_1_sqrt_2, frac_1_sqrt_2),
|
||||||
];
|
];
|
||||||
shapes.push(Shape::Path {
|
shapes.push(Shape::convex_polygon(points, fill, stroke));
|
||||||
points,
|
|
||||||
closed: true,
|
|
||||||
fill,
|
|
||||||
stroke,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
MarkerShape::Cross => {
|
MarkerShape::Cross => {
|
||||||
let diagonal1 = [
|
let diagonal1 = [
|
||||||
|
@ -1132,12 +1117,7 @@ impl PlotItem for Points {
|
||||||
MarkerShape::Up => {
|
MarkerShape::Up => {
|
||||||
let points =
|
let points =
|
||||||
vec![tf(0.0, -1.0), tf(-0.5 * sqrt_3, 0.5), tf(0.5 * sqrt_3, 0.5)];
|
vec![tf(0.0, -1.0), tf(-0.5 * sqrt_3, 0.5), tf(0.5 * sqrt_3, 0.5)];
|
||||||
shapes.push(Shape::Path {
|
shapes.push(Shape::convex_polygon(points, fill, stroke));
|
||||||
points,
|
|
||||||
closed: true,
|
|
||||||
fill,
|
|
||||||
stroke,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
MarkerShape::Down => {
|
MarkerShape::Down => {
|
||||||
let points = vec![
|
let points = vec![
|
||||||
|
@ -1145,22 +1125,12 @@ impl PlotItem for Points {
|
||||||
tf(-0.5 * sqrt_3, -0.5),
|
tf(-0.5 * sqrt_3, -0.5),
|
||||||
tf(0.5 * sqrt_3, -0.5),
|
tf(0.5 * sqrt_3, -0.5),
|
||||||
];
|
];
|
||||||
shapes.push(Shape::Path {
|
shapes.push(Shape::convex_polygon(points, fill, stroke));
|
||||||
points,
|
|
||||||
closed: true,
|
|
||||||
fill,
|
|
||||||
stroke,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
MarkerShape::Left => {
|
MarkerShape::Left => {
|
||||||
let points =
|
let points =
|
||||||
vec![tf(-1.0, 0.0), tf(0.5, -0.5 * sqrt_3), tf(0.5, 0.5 * sqrt_3)];
|
vec![tf(-1.0, 0.0), tf(0.5, -0.5 * sqrt_3), tf(0.5, 0.5 * sqrt_3)];
|
||||||
shapes.push(Shape::Path {
|
shapes.push(Shape::convex_polygon(points, fill, stroke));
|
||||||
points,
|
|
||||||
closed: true,
|
|
||||||
fill,
|
|
||||||
stroke,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
MarkerShape::Right => {
|
MarkerShape::Right => {
|
||||||
let points = vec![
|
let points = vec![
|
||||||
|
@ -1168,12 +1138,7 @@ impl PlotItem for Points {
|
||||||
tf(-0.5, -0.5 * sqrt_3),
|
tf(-0.5, -0.5 * sqrt_3),
|
||||||
tf(-0.5, 0.5 * sqrt_3),
|
tf(-0.5, 0.5 * sqrt_3),
|
||||||
];
|
];
|
||||||
shapes.push(Shape::Path {
|
shapes.push(Shape::convex_polygon(points, fill, stroke));
|
||||||
points,
|
|
||||||
closed: true,
|
|
||||||
fill,
|
|
||||||
stroke,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
MarkerShape::Asterisk => {
|
MarkerShape::Asterisk => {
|
||||||
let vertical = [tf(0.0, -1.0), tf(0.0, 1.0)];
|
let vertical = [tf(0.0, -1.0), tf(0.0, 1.0)];
|
||||||
|
|
|
@ -117,7 +117,7 @@ impl LegendEntry {
|
||||||
|
|
||||||
let painter = ui.painter();
|
let painter = ui.painter();
|
||||||
|
|
||||||
painter.add(Shape::Circle {
|
painter.add(epaint::CircleShape {
|
||||||
center: icon_rect.center(),
|
center: icon_rect.center(),
|
||||||
radius: icon_size * 0.5,
|
radius: icon_size * 0.5,
|
||||||
fill: visuals.bg_fill,
|
fill: visuals.bg_fill,
|
||||||
|
@ -130,12 +130,11 @@ impl LegendEntry {
|
||||||
} else {
|
} else {
|
||||||
*color
|
*color
|
||||||
};
|
};
|
||||||
painter.add(Shape::Circle {
|
painter.add(epaint::Shape::circle_filled(
|
||||||
center: icon_rect.center(),
|
icon_rect.center(),
|
||||||
radius: icon_size * 0.4,
|
icon_size * 0.4,
|
||||||
fill,
|
fill,
|
||||||
stroke: Default::default(),
|
));
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let text_position_x = if label_on_the_left {
|
let text_position_x = if label_on_the_left {
|
||||||
|
|
|
@ -404,7 +404,7 @@ impl Widget for Plot {
|
||||||
|
|
||||||
// Background
|
// Background
|
||||||
if show_background {
|
if show_background {
|
||||||
plot_painter.add(Shape::Rect {
|
plot_painter.add(epaint::RectShape {
|
||||||
rect,
|
rect,
|
||||||
corner_radius: 2.0,
|
corner_radius: 2.0,
|
||||||
fill: ui.visuals().extreme_bg_color,
|
fill: ui.visuals().extreme_bg_color,
|
||||||
|
|
|
@ -116,12 +116,10 @@ impl Widget for ProgressBar {
|
||||||
+ vec2(-corner_radius, 0.0)
|
+ vec2(-corner_radius, 0.0)
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
ui.painter().add(Shape::Path {
|
ui.painter().add(Shape::line(
|
||||||
points,
|
points,
|
||||||
closed: false,
|
Stroke::new(2.0, visuals.faint_bg_color),
|
||||||
fill: Color32::TRANSPARENT,
|
));
|
||||||
stroke: Stroke::new(2.0, visuals.faint_bg_color),
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(text_kind) = text {
|
if let Some(text_kind) = text {
|
||||||
|
|
|
@ -333,7 +333,7 @@ impl<'a> Slider<'a> {
|
||||||
let marker_center_x = self.x_from_value(value, x_range);
|
let marker_center_x = self.x_from_value(value, x_range);
|
||||||
|
|
||||||
let visuals = ui.style().interact(response);
|
let visuals = ui.style().interact(response);
|
||||||
ui.painter().add(Shape::Rect {
|
ui.painter().add(epaint::RectShape {
|
||||||
rect: rail_rect,
|
rect: rail_rect,
|
||||||
corner_radius: ui.visuals().widgets.inactive.corner_radius,
|
corner_radius: ui.visuals().widgets.inactive.corner_radius,
|
||||||
fill: ui.visuals().widgets.inactive.bg_fill,
|
fill: ui.visuals().widgets.inactive.bg_fill,
|
||||||
|
@ -344,7 +344,7 @@ impl<'a> Slider<'a> {
|
||||||
// stroke: ui.visuals().widgets.inactive.bg_stroke,
|
// stroke: ui.visuals().widgets.inactive.bg_stroke,
|
||||||
});
|
});
|
||||||
|
|
||||||
ui.painter().add(Shape::Circle {
|
ui.painter().add(epaint::CircleShape {
|
||||||
center: pos2(marker_center_x, rail_rect.center().y),
|
center: pos2(marker_center_x, rail_rect.center().y),
|
||||||
radius: handle_radius(rect) + visuals.expansion,
|
radius: handle_radius(rect) + visuals.expansion,
|
||||||
fill: visuals.bg_fill,
|
fill: visuals.bg_fill,
|
||||||
|
|
|
@ -425,7 +425,7 @@ impl<'t, S: TextBuffer> Widget for TextEdit<'t, S> {
|
||||||
let visuals = ui.style().interact(&response);
|
let visuals = ui.style().interact(&response);
|
||||||
let frame_rect = response.rect.expand(visuals.expansion);
|
let frame_rect = response.rect.expand(visuals.expansion);
|
||||||
let shape = if response.has_focus() {
|
let shape = if response.has_focus() {
|
||||||
Shape::Rect {
|
epaint::RectShape {
|
||||||
rect: frame_rect,
|
rect: frame_rect,
|
||||||
corner_radius: visuals.corner_radius,
|
corner_radius: visuals.corner_radius,
|
||||||
// fill: ui.visuals().selection.bg_fill,
|
// fill: ui.visuals().selection.bg_fill,
|
||||||
|
@ -433,7 +433,7 @@ impl<'t, S: TextBuffer> Widget for TextEdit<'t, S> {
|
||||||
stroke: ui.visuals().selection.stroke,
|
stroke: ui.visuals().selection.stroke,
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
Shape::Rect {
|
epaint::RectShape {
|
||||||
rect: frame_rect,
|
rect: frame_rect,
|
||||||
corner_radius: visuals.corner_radius,
|
corner_radius: visuals.corner_radius,
|
||||||
fill: ui.visuals().extreme_bg_color,
|
fill: ui.visuals().extreme_bg_color,
|
||||||
|
|
|
@ -70,10 +70,8 @@ pub fn criterion_benchmark(c: &mut Criterion) {
|
||||||
let wrap_width = 512.0;
|
let wrap_width = 512.0;
|
||||||
let text_style = egui::TextStyle::Body;
|
let text_style = egui::TextStyle::Body;
|
||||||
let color = egui::Color32::WHITE;
|
let color = egui::Color32::WHITE;
|
||||||
let fonts = egui::epaint::text::Fonts::from_definitions(
|
let fonts =
|
||||||
pixels_per_point,
|
egui::epaint::text::Fonts::new(pixels_per_point, egui::FontDefinitions::default());
|
||||||
egui::FontDefinitions::default(),
|
|
||||||
);
|
|
||||||
c.bench_function("text_layout_uncached", |b| {
|
c.bench_function("text_layout_uncached", |b| {
|
||||||
b.iter(|| {
|
b.iter(|| {
|
||||||
use egui::epaint::text::{layout, LayoutJob};
|
use egui::epaint::text::{layout, LayoutJob};
|
||||||
|
|
|
@ -65,7 +65,7 @@ pub fn drop_target<R>(
|
||||||
|
|
||||||
ui.painter().set(
|
ui.painter().set(
|
||||||
where_to_put_background,
|
where_to_put_background,
|
||||||
Shape::Rect {
|
epaint::RectShape {
|
||||||
corner_radius: style.corner_radius,
|
corner_radius: style.corner_radius,
|
||||||
fill,
|
fill,
|
||||||
stroke,
|
stroke,
|
||||||
|
|
|
@ -6,9 +6,10 @@ pub struct FrameHistory {
|
||||||
|
|
||||||
impl Default for FrameHistory {
|
impl Default for FrameHistory {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
let max_age: f64 = 1.0;
|
let max_age: f32 = 1.0;
|
||||||
|
let max_len = (max_age * 300.0).round() as usize;
|
||||||
Self {
|
Self {
|
||||||
frame_times: History::from_max_len_age((max_age * 300.0).round() as usize, max_age),
|
frame_times: History::new(0..max_len, max_age),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -73,12 +74,12 @@ impl FrameHistory {
|
||||||
let to_screen = emath::RectTransform::from_to(graph_rect, rect);
|
let to_screen = emath::RectTransform::from_to(graph_rect, rect);
|
||||||
|
|
||||||
let mut shapes = Vec::with_capacity(3 + 2 * history.len());
|
let mut shapes = Vec::with_capacity(3 + 2 * history.len());
|
||||||
shapes.push(Shape::Rect {
|
shapes.push(Shape::Rect(epaint::RectShape {
|
||||||
rect,
|
rect,
|
||||||
corner_radius: style.corner_radius,
|
corner_radius: style.corner_radius,
|
||||||
fill: ui.visuals().extreme_bg_color,
|
fill: ui.visuals().extreme_bg_color,
|
||||||
stroke: ui.style().noninteractive().bg_stroke,
|
stroke: ui.style().noninteractive().bg_stroke,
|
||||||
});
|
}));
|
||||||
|
|
||||||
let rect = rect.shrink(4.0);
|
let rect = rect.shrink(4.0);
|
||||||
let color = ui.visuals().text_color();
|
let color = ui.visuals().text_color();
|
||||||
|
|
|
@ -34,7 +34,6 @@ use {
|
||||||
};
|
};
|
||||||
|
|
||||||
pub use copypasta::ClipboardContext;
|
pub use copypasta::ClipboardContext;
|
||||||
use std::borrow::BorrowMut; // TODO: remove
|
|
||||||
|
|
||||||
pub struct GliumInputState {
|
pub struct GliumInputState {
|
||||||
pub pointer_pos_in_points: Option<Pos2>,
|
pub pointer_pos_in_points: Option<Pos2>,
|
||||||
|
@ -556,10 +555,21 @@ impl EguiGlium {
|
||||||
&self.egui_ctx
|
&self.egui_ctx
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn painter_mut(&mut self) -> &mut crate::Painter {
|
||||||
|
&mut self.painter
|
||||||
|
}
|
||||||
|
|
||||||
pub fn ctx_and_painter_mut(&mut self) -> (&egui::CtxRef, &mut crate::Painter) {
|
pub fn ctx_and_painter_mut(&mut self) -> (&egui::CtxRef, &mut crate::Painter) {
|
||||||
(&self.egui_ctx, &mut self.painter)
|
(&self.egui_ctx, &mut self.painter)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn pixels_per_point(&self) -> f32 {
|
||||||
|
self.input_state
|
||||||
|
.raw
|
||||||
|
.pixels_per_point
|
||||||
|
.unwrap_or_else(|| self.egui_ctx.pixels_per_point())
|
||||||
|
}
|
||||||
|
|
||||||
pub fn on_event(&mut self, event: &glium::glutin::event::WindowEvent<'_>) {
|
pub fn on_event(&mut self, event: &glium::glutin::event::WindowEvent<'_>) {
|
||||||
crate::input_to_egui(
|
crate::input_to_egui(
|
||||||
self.egui_ctx.pixels_per_point(),
|
self.egui_ctx.pixels_per_point(),
|
||||||
|
@ -575,13 +585,19 @@ impl EguiGlium {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn begin_frame(&mut self, display: &glium::Display) {
|
pub fn begin_frame(&mut self, display: &glium::Display) {
|
||||||
let pixels_per_point = self
|
let raw_input = self.take_raw_input(display);
|
||||||
.input_state
|
self.begin_frame_with_input(raw_input);
|
||||||
.raw
|
}
|
||||||
.pixels_per_point
|
|
||||||
.unwrap_or_else(|| self.egui_ctx.pixels_per_point());
|
|
||||||
|
|
||||||
self.input_state.raw.time = Some(self.start_time.elapsed().as_nanos() as f64 * 1e-9);
|
pub fn begin_frame_with_input(&mut self, raw_input: RawInput) {
|
||||||
|
self.egui_ctx.begin_frame(raw_input);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Prepare for a new frame. Normally you would call [`Self::begin_frame`] instead.
|
||||||
|
pub fn take_raw_input(&mut self, display: &glium::Display) -> egui::RawInput {
|
||||||
|
let pixels_per_point = self.pixels_per_point();
|
||||||
|
|
||||||
|
self.input_state.raw.time = Some(self.start_time.elapsed().as_secs_f64());
|
||||||
|
|
||||||
// On Windows, a minimized window will have 0 width and height.
|
// On Windows, a minimized window will have 0 width and height.
|
||||||
// See: https://github.com/rust-windowing/winit/issues/208
|
// See: https://github.com/rust-windowing/winit/issues/208
|
||||||
|
@ -596,7 +612,7 @@ impl EguiGlium {
|
||||||
None
|
None
|
||||||
};
|
};
|
||||||
|
|
||||||
self.egui_ctx.begin_frame(self.input_state.raw.take());
|
self.input_state.raw.take()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns `needs_repaint` and shapes to draw.
|
/// Returns `needs_repaint` and shapes to draw.
|
||||||
|
@ -605,10 +621,16 @@ impl EguiGlium {
|
||||||
display: &glium::Display,
|
display: &glium::Display,
|
||||||
) -> (bool, Vec<egui::epaint::ClippedShape>) {
|
) -> (bool, Vec<egui::epaint::ClippedShape>) {
|
||||||
let (egui_output, shapes) = self.egui_ctx.end_frame();
|
let (egui_output, shapes) = self.egui_ctx.end_frame();
|
||||||
|
let needs_repaint = egui_output.needs_repaint;
|
||||||
|
self.handle_output(display, egui_output);
|
||||||
|
(needs_repaint, shapes)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn handle_output(&mut self, display: &glium::Display, egui_output: egui::Output) {
|
||||||
if self.egui_ctx.memory().options.screen_reader {
|
if self.egui_ctx.memory().options.screen_reader {
|
||||||
self.screen_reader.speak(&egui_output.events_description());
|
self.screen_reader.speak(&egui_output.events_description());
|
||||||
}
|
}
|
||||||
|
|
||||||
if self.current_cursor_icon != egui_output.cursor_icon {
|
if self.current_cursor_icon != egui_output.cursor_icon {
|
||||||
// call only when changed to prevent flickering near frame boundary
|
// call only when changed to prevent flickering near frame boundary
|
||||||
// when Windows OS tries to control cursor icon for window resizing
|
// when Windows OS tries to control cursor icon for window resizing
|
||||||
|
@ -616,11 +638,7 @@ impl EguiGlium {
|
||||||
self.current_cursor_icon = egui_output.cursor_icon;
|
self.current_cursor_icon = egui_output.cursor_icon;
|
||||||
}
|
}
|
||||||
|
|
||||||
let needs_repaint = egui_output.needs_repaint;
|
|
||||||
|
|
||||||
handle_output(egui_output, self.clipboard.as_mut(), display);
|
handle_output(egui_output, self.clipboard.as_mut(), display);
|
||||||
|
|
||||||
(needs_repaint, shapes)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn paint<T: glium::Surface>(
|
pub fn paint<T: glium::Surface>(
|
||||||
|
@ -638,8 +656,4 @@ impl EguiGlium {
|
||||||
&self.egui_ctx.texture(),
|
&self.egui_ctx.texture(),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn painter_mut(&mut self) -> &mut Painter {
|
|
||||||
self.painter.borrow_mut()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -88,6 +88,15 @@ impl Rect {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Bounding-box around the points
|
||||||
|
pub fn from_points(points: &[Pos2]) -> Self {
|
||||||
|
let mut rect = Rect::NOTHING;
|
||||||
|
for &p in points {
|
||||||
|
rect.extend_with(p);
|
||||||
|
}
|
||||||
|
rect
|
||||||
|
}
|
||||||
|
|
||||||
/// A `Rect` that contains every point to the right of the given X coordinate.
|
/// A `Rect` that contains every point to the right of the given X coordinate.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn everything_right_of(left_x: f32) -> Self {
|
pub fn everything_right_of(left_x: f32) -> Self {
|
||||||
|
@ -165,15 +174,6 @@ impl Rect {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The intersection of two `Rect`, i.e. the area covered by both.
|
|
||||||
#[must_use]
|
|
||||||
pub fn intersect(self, other: Rect) -> Self {
|
|
||||||
Self {
|
|
||||||
min: self.min.max(other.min),
|
|
||||||
max: self.max.min(other.max),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[must_use]
|
#[must_use]
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn intersects(self, other: Rect) -> bool {
|
pub fn intersects(self, other: Rect) -> bool {
|
||||||
|
@ -236,7 +236,10 @@ impl Rect {
|
||||||
self.max.y = self.max.y.max(y);
|
self.max.y = self.max.y.max(y);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The union of two bounding rectangle, i.e. the minimum `Rect`
|
||||||
|
/// that contains both input rectangles.
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
|
#[must_use]
|
||||||
pub fn union(self, other: Rect) -> Rect {
|
pub fn union(self, other: Rect) -> Rect {
|
||||||
Rect {
|
Rect {
|
||||||
min: self.min.min(other.min),
|
min: self.min.min(other.min),
|
||||||
|
@ -244,6 +247,16 @@ impl Rect {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The intersection of two `Rect`, i.e. the area covered by both.
|
||||||
|
#[inline]
|
||||||
|
#[must_use]
|
||||||
|
pub fn intersect(self, other: Rect) -> Self {
|
||||||
|
Self {
|
||||||
|
min: self.min.max(other.min),
|
||||||
|
max: self.max.min(other.max),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub fn center(&self) -> Pos2 {
|
pub fn center(&self) -> Pos2 {
|
||||||
Pos2 {
|
Pos2 {
|
||||||
|
|
|
@ -87,7 +87,7 @@ pub use {
|
||||||
color::{Color32, Rgba},
|
color::{Color32, Rgba},
|
||||||
mesh::{Mesh, Mesh16, Vertex},
|
mesh::{Mesh, Mesh16, Vertex},
|
||||||
shadow::Shadow,
|
shadow::Shadow,
|
||||||
shape::{Shape, TextShape},
|
shape::{CircleShape, PathShape, RectShape, Shape, TextShape},
|
||||||
stats::PaintStats,
|
stats::PaintStats,
|
||||||
stroke::Stroke,
|
stroke::Stroke,
|
||||||
tessellator::{TessellationOptions, Tessellator},
|
tessellator::{TessellationOptions, Tessellator},
|
||||||
|
@ -95,6 +95,8 @@ pub use {
|
||||||
texture_atlas::{Texture, TextureAtlas},
|
texture_atlas::{Texture, TextureAtlas},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
pub use emath::{pos2, vec2, Pos2, Rect, Vec2};
|
||||||
|
|
||||||
pub use ahash;
|
pub use ahash;
|
||||||
pub use emath;
|
pub use emath;
|
||||||
|
|
||||||
|
@ -106,6 +108,7 @@ pub const WHITE_UV: emath::Pos2 = emath::pos2(0.0, 0.0);
|
||||||
|
|
||||||
/// What texture to use in a [`Mesh`] mesh.
|
/// What texture to use in a [`Mesh`] mesh.
|
||||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
|
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
|
||||||
|
#[cfg_attr(feature = "persistence", derive(serde::Deserialize, serde::Serialize))]
|
||||||
pub enum TextureId {
|
pub enum TextureId {
|
||||||
/// The egui font texture.
|
/// The egui font texture.
|
||||||
/// If you don't want to use a texture, pick this and the [`WHITE_UV`] for uv-coord.
|
/// If you don't want to use a texture, pick this and the [`WHITE_UV`] for uv-coord.
|
||||||
|
@ -122,14 +125,6 @@ impl Default for TextureId {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) struct PaintRect {
|
|
||||||
pub rect: emath::Rect,
|
|
||||||
/// How rounded the corners are. Use `0.0` for no rounding.
|
|
||||||
pub corner_radius: f32,
|
|
||||||
pub fill: Color32,
|
|
||||||
pub stroke: Stroke,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A [`Shape`] within a clip rectangle.
|
/// A [`Shape`] within a clip rectangle.
|
||||||
///
|
///
|
||||||
/// Everything is using logical points.
|
/// Everything is using logical points.
|
||||||
|
@ -146,6 +141,7 @@ pub struct ClippedShape(
|
||||||
///
|
///
|
||||||
/// Everything is using logical points.
|
/// Everything is using logical points.
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
|
#[cfg_attr(feature = "persistence", derive(serde::Deserialize, serde::Serialize))]
|
||||||
pub struct ClippedMesh(
|
pub struct ClippedMesh(
|
||||||
/// Clip / scissor rectangle.
|
/// Clip / scissor rectangle.
|
||||||
/// Only show the part of the [`Mesh`] that falls within this.
|
/// Only show the part of the [`Mesh`] that falls within this.
|
||||||
|
|
|
@ -6,6 +6,7 @@ use emath::*;
|
||||||
/// Should be friendly to send to GPU as is.
|
/// Should be friendly to send to GPU as is.
|
||||||
#[repr(C)]
|
#[repr(C)]
|
||||||
#[derive(Clone, Copy, Debug, Default, PartialEq)]
|
#[derive(Clone, Copy, Debug, Default, PartialEq)]
|
||||||
|
#[cfg_attr(feature = "persistence", derive(serde::Deserialize, serde::Serialize))]
|
||||||
pub struct Vertex {
|
pub struct Vertex {
|
||||||
/// Logical pixel coordinates (points).
|
/// Logical pixel coordinates (points).
|
||||||
/// (0,0) is the top left corner of the screen.
|
/// (0,0) is the top left corner of the screen.
|
||||||
|
@ -22,6 +23,7 @@ pub struct Vertex {
|
||||||
|
|
||||||
/// Textured triangles in two dimensions.
|
/// Textured triangles in two dimensions.
|
||||||
#[derive(Clone, Debug, Default, PartialEq)]
|
#[derive(Clone, Debug, Default, PartialEq)]
|
||||||
|
#[cfg_attr(feature = "persistence", derive(serde::Deserialize, serde::Serialize))]
|
||||||
pub struct Mesh {
|
pub struct Mesh {
|
||||||
/// Draw as triangles (i.e. the length is always multiple of three).
|
/// Draw as triangles (i.e. the length is always multiple of three).
|
||||||
///
|
///
|
||||||
|
|
|
@ -52,12 +52,11 @@ impl Shadow {
|
||||||
let Self { extrusion, color } = *self;
|
let Self { extrusion, color } = *self;
|
||||||
|
|
||||||
use crate::tessellator::*;
|
use crate::tessellator::*;
|
||||||
let rect = PaintRect {
|
let rect = RectShape::filled(
|
||||||
rect: rect.expand(0.5 * extrusion),
|
rect.expand(0.5 * extrusion),
|
||||||
corner_radius: corner_radius + 0.5 * extrusion,
|
corner_radius + 0.5 * extrusion,
|
||||||
fill: color,
|
color,
|
||||||
stroke: Default::default(),
|
);
|
||||||
};
|
|
||||||
let mut tessellator = Tessellator::from_options(TessellationOptions {
|
let mut tessellator = Tessellator::from_options(TessellationOptions {
|
||||||
aa_size: extrusion,
|
aa_size: extrusion,
|
||||||
anti_alias: true,
|
anti_alias: true,
|
||||||
|
|
|
@ -14,32 +14,13 @@ pub enum Shape {
|
||||||
/// Recursively nest more shapes - sometimes a convenience to be able to do.
|
/// Recursively nest more shapes - sometimes a convenience to be able to do.
|
||||||
/// For performance reasons it is better to avoid it.
|
/// For performance reasons it is better to avoid it.
|
||||||
Vec(Vec<Shape>),
|
Vec(Vec<Shape>),
|
||||||
Circle {
|
Circle(CircleShape),
|
||||||
center: Pos2,
|
|
||||||
radius: f32,
|
|
||||||
fill: Color32,
|
|
||||||
stroke: Stroke,
|
|
||||||
},
|
|
||||||
LineSegment {
|
LineSegment {
|
||||||
points: [Pos2; 2],
|
points: [Pos2; 2],
|
||||||
stroke: Stroke,
|
stroke: Stroke,
|
||||||
},
|
},
|
||||||
Path {
|
Path(PathShape),
|
||||||
points: Vec<Pos2>,
|
Rect(RectShape),
|
||||||
/// If true, connect the first and last of the points together.
|
|
||||||
/// This is required if `fill != TRANSPARENT`.
|
|
||||||
closed: bool,
|
|
||||||
/// Fill is only supported for convex polygons.
|
|
||||||
fill: Color32,
|
|
||||||
stroke: Stroke,
|
|
||||||
},
|
|
||||||
Rect {
|
|
||||||
rect: Rect,
|
|
||||||
/// How rounded the corners are. Use `0.0` for no rounding.
|
|
||||||
corner_radius: f32,
|
|
||||||
fill: Color32,
|
|
||||||
stroke: Stroke,
|
|
||||||
},
|
|
||||||
Text(TextShape),
|
Text(TextShape),
|
||||||
Mesh(Mesh),
|
Mesh(Mesh),
|
||||||
}
|
}
|
||||||
|
@ -48,6 +29,7 @@ pub enum Shape {
|
||||||
impl Shape {
|
impl Shape {
|
||||||
/// A line between two points.
|
/// A line between two points.
|
||||||
/// More efficient than calling [`Self::line`].
|
/// More efficient than calling [`Self::line`].
|
||||||
|
#[inline]
|
||||||
pub fn line_segment(points: [Pos2; 2], stroke: impl Into<Stroke>) -> Self {
|
pub fn line_segment(points: [Pos2; 2], stroke: impl Into<Stroke>) -> Self {
|
||||||
Self::LineSegment {
|
Self::LineSegment {
|
||||||
points,
|
points,
|
||||||
|
@ -58,23 +40,15 @@ impl Shape {
|
||||||
/// A line through many points.
|
/// A line through many points.
|
||||||
///
|
///
|
||||||
/// Use [`Self::line_segment`] instead if your line only connects two points.
|
/// Use [`Self::line_segment`] instead if your line only connects two points.
|
||||||
|
#[inline]
|
||||||
pub fn line(points: Vec<Pos2>, stroke: impl Into<Stroke>) -> Self {
|
pub fn line(points: Vec<Pos2>, stroke: impl Into<Stroke>) -> Self {
|
||||||
Self::Path {
|
Self::Path(PathShape::line(points, stroke))
|
||||||
points,
|
|
||||||
closed: false,
|
|
||||||
fill: Default::default(),
|
|
||||||
stroke: stroke.into(),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A line that closes back to the start point again.
|
/// A line that closes back to the start point again.
|
||||||
|
#[inline]
|
||||||
pub fn closed_line(points: Vec<Pos2>, stroke: impl Into<Stroke>) -> Self {
|
pub fn closed_line(points: Vec<Pos2>, stroke: impl Into<Stroke>) -> Self {
|
||||||
Self::Path {
|
Self::Path(PathShape::closed_line(points, stroke))
|
||||||
points,
|
|
||||||
closed: true,
|
|
||||||
fill: Default::default(),
|
|
||||||
stroke: stroke.into(),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Turn a line into equally spaced dots.
|
/// Turn a line into equally spaced dots.
|
||||||
|
@ -102,53 +76,33 @@ impl Shape {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A convex polygon with a fill and optional stroke.
|
/// A convex polygon with a fill and optional stroke.
|
||||||
|
#[inline]
|
||||||
pub fn convex_polygon(
|
pub fn convex_polygon(
|
||||||
points: Vec<Pos2>,
|
points: Vec<Pos2>,
|
||||||
fill: impl Into<Color32>,
|
fill: impl Into<Color32>,
|
||||||
stroke: impl Into<Stroke>,
|
stroke: impl Into<Stroke>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
Self::Path {
|
Self::Path(PathShape::convex_polygon(points, fill, stroke))
|
||||||
points,
|
|
||||||
closed: true,
|
|
||||||
fill: fill.into(),
|
|
||||||
stroke: stroke.into(),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
pub fn circle_filled(center: Pos2, radius: f32, fill_color: impl Into<Color32>) -> Self {
|
pub fn circle_filled(center: Pos2, radius: f32, fill_color: impl Into<Color32>) -> Self {
|
||||||
Self::Circle {
|
Self::Circle(CircleShape::filled(center, radius, fill_color))
|
||||||
center,
|
|
||||||
radius,
|
|
||||||
fill: fill_color.into(),
|
|
||||||
stroke: Default::default(),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
pub fn circle_stroke(center: Pos2, radius: f32, stroke: impl Into<Stroke>) -> Self {
|
pub fn circle_stroke(center: Pos2, radius: f32, stroke: impl Into<Stroke>) -> Self {
|
||||||
Self::Circle {
|
Self::Circle(CircleShape::stroke(center, radius, stroke))
|
||||||
center,
|
|
||||||
radius,
|
|
||||||
fill: Default::default(),
|
|
||||||
stroke: stroke.into(),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
pub fn rect_filled(rect: Rect, corner_radius: f32, fill_color: impl Into<Color32>) -> Self {
|
pub fn rect_filled(rect: Rect, corner_radius: f32, fill_color: impl Into<Color32>) -> Self {
|
||||||
Self::Rect {
|
Self::Rect(RectShape::filled(rect, corner_radius, fill_color))
|
||||||
rect,
|
|
||||||
corner_radius,
|
|
||||||
fill: fill_color.into(),
|
|
||||||
stroke: Default::default(),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
pub fn rect_stroke(rect: Rect, corner_radius: f32, stroke: impl Into<Stroke>) -> Self {
|
pub fn rect_stroke(rect: Rect, corner_radius: f32, stroke: impl Into<Stroke>) -> Self {
|
||||||
Self::Rect {
|
Self::Rect(RectShape::stroke(rect, corner_radius, stroke))
|
||||||
rect,
|
|
||||||
corner_radius,
|
|
||||||
fill: Default::default(),
|
|
||||||
stroke: stroke.into(),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(clippy::needless_pass_by_value)]
|
#[allow(clippy::needless_pass_by_value)]
|
||||||
|
@ -165,6 +119,7 @@ impl Shape {
|
||||||
Self::galley(rect.min, galley)
|
Self::galley(rect.min, galley)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
pub fn galley(pos: Pos2, galley: std::sync::Arc<Galley>) -> Self {
|
pub fn galley(pos: Pos2, galley: std::sync::Arc<Galley>) -> Self {
|
||||||
TextShape::new(pos, galley).into()
|
TextShape::new(pos, galley).into()
|
||||||
}
|
}
|
||||||
|
@ -172,7 +127,165 @@ impl Shape {
|
||||||
|
|
||||||
// ----------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------
|
||||||
|
|
||||||
/// How to draw some text on screen.
|
/// How to paint a circle.
|
||||||
|
#[derive(Copy, Clone, Debug, PartialEq)]
|
||||||
|
#[cfg_attr(feature = "persistence", derive(serde::Deserialize, serde::Serialize))]
|
||||||
|
pub struct CircleShape {
|
||||||
|
pub center: Pos2,
|
||||||
|
pub radius: f32,
|
||||||
|
pub fill: Color32,
|
||||||
|
pub stroke: Stroke,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CircleShape {
|
||||||
|
#[inline]
|
||||||
|
pub fn filled(center: Pos2, radius: f32, fill_color: impl Into<Color32>) -> Self {
|
||||||
|
Self {
|
||||||
|
center,
|
||||||
|
radius,
|
||||||
|
fill: fill_color.into(),
|
||||||
|
stroke: Default::default(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn stroke(center: Pos2, radius: f32, stroke: impl Into<Stroke>) -> Self {
|
||||||
|
Self {
|
||||||
|
center,
|
||||||
|
radius,
|
||||||
|
fill: Default::default(),
|
||||||
|
stroke: stroke.into(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<CircleShape> for Shape {
|
||||||
|
#[inline(always)]
|
||||||
|
fn from(shape: CircleShape) -> Self {
|
||||||
|
Self::Circle(shape)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
/// A path which can be stroked and/or filled (if closed).
|
||||||
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
|
#[cfg_attr(feature = "persistence", derive(serde::Deserialize, serde::Serialize))]
|
||||||
|
pub struct PathShape {
|
||||||
|
pub points: Vec<Pos2>,
|
||||||
|
/// If true, connect the first and last of the points together.
|
||||||
|
/// This is required if `fill != TRANSPARENT`.
|
||||||
|
pub closed: bool,
|
||||||
|
/// Fill is only supported for convex polygons.
|
||||||
|
pub fill: Color32,
|
||||||
|
pub stroke: Stroke,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PathShape {
|
||||||
|
/// A line through many points.
|
||||||
|
///
|
||||||
|
/// Use [`Shape::line_segment`] instead if your line only connects two points.
|
||||||
|
#[inline]
|
||||||
|
pub fn line(points: Vec<Pos2>, stroke: impl Into<Stroke>) -> Self {
|
||||||
|
PathShape {
|
||||||
|
points,
|
||||||
|
closed: false,
|
||||||
|
fill: Default::default(),
|
||||||
|
stroke: stroke.into(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A line that closes back to the start point again.
|
||||||
|
#[inline]
|
||||||
|
pub fn closed_line(points: Vec<Pos2>, stroke: impl Into<Stroke>) -> Self {
|
||||||
|
PathShape {
|
||||||
|
points,
|
||||||
|
closed: true,
|
||||||
|
fill: Default::default(),
|
||||||
|
stroke: stroke.into(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A convex polygon with a fill and optional stroke.
|
||||||
|
#[inline]
|
||||||
|
pub fn convex_polygon(
|
||||||
|
points: Vec<Pos2>,
|
||||||
|
fill: impl Into<Color32>,
|
||||||
|
stroke: impl Into<Stroke>,
|
||||||
|
) -> Self {
|
||||||
|
PathShape {
|
||||||
|
points,
|
||||||
|
closed: true,
|
||||||
|
fill: fill.into(),
|
||||||
|
stroke: stroke.into(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Screen-space bounding rectangle.
|
||||||
|
#[inline]
|
||||||
|
pub fn bounding_rect(&self) -> Rect {
|
||||||
|
Rect::from_points(&self.points).expand(self.stroke.width)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<PathShape> for Shape {
|
||||||
|
#[inline(always)]
|
||||||
|
fn from(shape: PathShape) -> Self {
|
||||||
|
Self::Path(shape)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
/// How to paint a rectangle.
|
||||||
|
#[derive(Copy, Clone, Debug, PartialEq)]
|
||||||
|
#[cfg_attr(feature = "persistence", derive(serde::Deserialize, serde::Serialize))]
|
||||||
|
pub struct RectShape {
|
||||||
|
pub rect: Rect,
|
||||||
|
/// How rounded the corners are. Use `0.0` for no rounding.
|
||||||
|
pub corner_radius: f32,
|
||||||
|
pub fill: Color32,
|
||||||
|
pub stroke: Stroke,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RectShape {
|
||||||
|
#[inline]
|
||||||
|
pub fn filled(rect: Rect, corner_radius: f32, fill_color: impl Into<Color32>) -> Self {
|
||||||
|
Self {
|
||||||
|
rect,
|
||||||
|
corner_radius,
|
||||||
|
fill: fill_color.into(),
|
||||||
|
stroke: Default::default(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn stroke(rect: Rect, corner_radius: f32, stroke: impl Into<Stroke>) -> Self {
|
||||||
|
Self {
|
||||||
|
rect,
|
||||||
|
corner_radius,
|
||||||
|
fill: Default::default(),
|
||||||
|
stroke: stroke.into(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Screen-space bounding rectangle.
|
||||||
|
#[inline]
|
||||||
|
pub fn bounding_rect(&self) -> Rect {
|
||||||
|
self.rect.expand(self.stroke.width)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<RectShape> for Shape {
|
||||||
|
#[inline(always)]
|
||||||
|
fn from(shape: RectShape) -> Self {
|
||||||
|
Self::Rect(shape)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
/// How to paint some text on screen.
|
||||||
#[derive(Clone, Debug, PartialEq)]
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
pub struct TextShape {
|
pub struct TextShape {
|
||||||
/// Top left corner of the first character.
|
/// Top left corner of the first character.
|
||||||
|
@ -206,12 +319,18 @@ impl TextShape {
|
||||||
angle: 0.0,
|
angle: 0.0,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Screen-space bounding rectangle.
|
||||||
|
#[inline]
|
||||||
|
pub fn bounding_rect(&self) -> Rect {
|
||||||
|
self.galley.mesh_bounds.translate(self.pos.to_vec2())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<TextShape> for Shape {
|
impl From<TextShape> for Shape {
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
fn from(text_shape: TextShape) -> Self {
|
fn from(shape: TextShape) -> Self {
|
||||||
Self::Text(text_shape)
|
Self::Text(shape)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -259,7 +378,7 @@ fn dashes_from_line(
|
||||||
let new_point = start + vector * (position_on_segment / segment_length);
|
let new_point = start + vector * (position_on_segment / segment_length);
|
||||||
if drawing_dash {
|
if drawing_dash {
|
||||||
// This is the end point.
|
// This is the end point.
|
||||||
if let Shape::Path { points, .. } = shapes.last_mut().unwrap() {
|
if let Shape::Path(PathShape { points, .. }) = shapes.last_mut().unwrap() {
|
||||||
points.push(new_point);
|
points.push(new_point);
|
||||||
}
|
}
|
||||||
position_on_segment += gap_length;
|
position_on_segment += gap_length;
|
||||||
|
@ -272,7 +391,7 @@ fn dashes_from_line(
|
||||||
}
|
}
|
||||||
// If the segment ends and the dash is not finished, add the segment's end point.
|
// If the segment ends and the dash is not finished, add the segment's end point.
|
||||||
if drawing_dash {
|
if drawing_dash {
|
||||||
if let Shape::Path { points, .. } = shapes.last_mut().unwrap() {
|
if let Shape::Path(PathShape { points, .. }) = shapes.last_mut().unwrap() {
|
||||||
points.push(end);
|
points.push(end);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -305,21 +424,21 @@ impl Shape {
|
||||||
shape.translate(delta);
|
shape.translate(delta);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Shape::Circle { center, .. } => {
|
Shape::Circle(circle_shape) => {
|
||||||
*center += delta;
|
circle_shape.center += delta;
|
||||||
}
|
}
|
||||||
Shape::LineSegment { points, .. } => {
|
Shape::LineSegment { points, .. } => {
|
||||||
for p in points {
|
for p in points {
|
||||||
*p += delta;
|
*p += delta;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Shape::Path { points, .. } => {
|
Shape::Path(path_shape) => {
|
||||||
for p in points {
|
for p in &mut path_shape.points {
|
||||||
*p += delta;
|
*p += delta;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Shape::Rect { rect, .. } => {
|
Shape::Rect(rect_shape) => {
|
||||||
*rect = rect.translate(delta);
|
rect_shape.rect = rect_shape.rect.translate(delta);
|
||||||
}
|
}
|
||||||
Shape::Text(text_shape) => {
|
Shape::Text(text_shape) => {
|
||||||
text_shape.pos += delta;
|
text_shape.pos += delta;
|
||||||
|
|
|
@ -9,20 +9,20 @@ pub fn adjust_colors(shape: &mut Shape, adjust_color: &impl Fn(&mut Color32)) {
|
||||||
adjust_colors(shape, adjust_color)
|
adjust_colors(shape, adjust_color)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Shape::Circle { fill, stroke, .. } => {
|
Shape::Circle(circle_shape) => {
|
||||||
adjust_color(fill);
|
adjust_color(&mut circle_shape.fill);
|
||||||
adjust_color(&mut stroke.color);
|
adjust_color(&mut circle_shape.stroke.color);
|
||||||
}
|
}
|
||||||
Shape::LineSegment { stroke, .. } => {
|
Shape::LineSegment { stroke, .. } => {
|
||||||
adjust_color(&mut stroke.color);
|
adjust_color(&mut stroke.color);
|
||||||
}
|
}
|
||||||
Shape::Path { fill, stroke, .. } => {
|
Shape::Path(path_shape) => {
|
||||||
adjust_color(fill);
|
adjust_color(&mut path_shape.fill);
|
||||||
adjust_color(&mut stroke.color);
|
adjust_color(&mut path_shape.stroke.color);
|
||||||
}
|
}
|
||||||
Shape::Rect { fill, stroke, .. } => {
|
Shape::Rect(rect_shape) => {
|
||||||
adjust_color(fill);
|
adjust_color(&mut rect_shape.fill);
|
||||||
adjust_color(&mut stroke.color);
|
adjust_color(&mut rect_shape.stroke.color);
|
||||||
}
|
}
|
||||||
Shape::Text(text_shape) => {
|
Shape::Text(text_shape) => {
|
||||||
if let Some(override_text_color) = &mut text_shape.override_text_color {
|
if let Some(override_text_color) = &mut text_shape.override_text_color {
|
||||||
|
|
|
@ -163,6 +163,9 @@ pub struct PaintStats {
|
||||||
pub shape_mesh: AllocInfo,
|
pub shape_mesh: AllocInfo,
|
||||||
pub shape_vec: AllocInfo,
|
pub shape_vec: AllocInfo,
|
||||||
|
|
||||||
|
pub text_shape_vertices: AllocInfo,
|
||||||
|
pub text_shape_indices: AllocInfo,
|
||||||
|
|
||||||
/// Number of separate clip rectangles
|
/// Number of separate clip rectangles
|
||||||
pub clipped_meshes: AllocInfo,
|
pub clipped_meshes: AllocInfo,
|
||||||
pub vertices: AllocInfo,
|
pub vertices: AllocInfo,
|
||||||
|
@ -195,11 +198,16 @@ impl PaintStats {
|
||||||
Shape::Noop | Shape::Circle { .. } | Shape::LineSegment { .. } | Shape::Rect { .. } => {
|
Shape::Noop | Shape::Circle { .. } | Shape::LineSegment { .. } | Shape::Rect { .. } => {
|
||||||
Default::default()
|
Default::default()
|
||||||
}
|
}
|
||||||
Shape::Path { points, .. } => {
|
Shape::Path(path_shape) => {
|
||||||
self.shape_path += AllocInfo::from_slice(points);
|
self.shape_path += AllocInfo::from_slice(&path_shape.points);
|
||||||
}
|
}
|
||||||
Shape::Text(text_shape) => {
|
Shape::Text(text_shape) => {
|
||||||
self.shape_text += AllocInfo::from_galley(&text_shape.galley);
|
self.shape_text += AllocInfo::from_galley(&text_shape.galley);
|
||||||
|
|
||||||
|
for row in &text_shape.galley.rows {
|
||||||
|
self.text_shape_indices += AllocInfo::from_slice(&row.visuals.mesh.indices);
|
||||||
|
self.text_shape_vertices += AllocInfo::from_slice(&row.visuals.mesh.vertices);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Shape::Mesh(mesh) => {
|
Shape::Mesh(mesh) => {
|
||||||
self.shape_mesh += AllocInfo::from_mesh(mesh);
|
self.shape_mesh += AllocInfo::from_mesh(mesh);
|
||||||
|
|
|
@ -26,6 +26,12 @@ impl Stroke {
|
||||||
color: color.into(),
|
color: color.into(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// True if width is zero or color is transparent
|
||||||
|
#[inline]
|
||||||
|
pub fn is_empty(&self) -> bool {
|
||||||
|
self.width <= 0.0 || self.color == Color32::TRANSPARENT
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<Color> From<(f32, Color)> for Stroke
|
impl<Color> From<(f32, Color)> for Stroke
|
||||||
|
|
|
@ -541,12 +541,12 @@ impl Tessellator {
|
||||||
self.tessellate_shape(tex_size, shape, out)
|
self.tessellate_shape(tex_size, shape, out)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Shape::Circle {
|
Shape::Circle(CircleShape {
|
||||||
center,
|
center,
|
||||||
radius,
|
radius,
|
||||||
fill,
|
fill,
|
||||||
stroke,
|
stroke,
|
||||||
} => {
|
}) => {
|
||||||
if radius <= 0.0 {
|
if radius <= 0.0 {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -563,24 +563,71 @@ impl Tessellator {
|
||||||
self.scratchpad_path.stroke_closed(stroke, options, out);
|
self.scratchpad_path.stroke_closed(stroke, options, out);
|
||||||
}
|
}
|
||||||
Shape::Mesh(mesh) => {
|
Shape::Mesh(mesh) => {
|
||||||
if mesh.is_valid() {
|
if !mesh.is_valid() {
|
||||||
out.append(mesh);
|
|
||||||
} else {
|
|
||||||
crate::epaint_assert!(false, "Invalid Mesh in Shape::Mesh");
|
crate::epaint_assert!(false, "Invalid Mesh in Shape::Mesh");
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if options.coarse_tessellation_culling && !clip_rect.intersects(mesh.calc_bounds())
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
out.append(mesh);
|
||||||
}
|
}
|
||||||
Shape::LineSegment { points, stroke } => {
|
Shape::LineSegment { points, stroke } => {
|
||||||
|
if stroke.is_empty() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if options.coarse_tessellation_culling
|
||||||
|
&& !clip_rect
|
||||||
|
.intersects(Rect::from_two_pos(points[0], points[1]).expand(stroke.width))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
self.scratchpad_path.clear();
|
self.scratchpad_path.clear();
|
||||||
self.scratchpad_path.add_line_segment(points);
|
self.scratchpad_path.add_line_segment(points);
|
||||||
self.scratchpad_path.stroke_open(stroke, options, out);
|
self.scratchpad_path.stroke_open(stroke, options, out);
|
||||||
}
|
}
|
||||||
Shape::Path {
|
Shape::Path(path_shape) => {
|
||||||
|
self.tessellate_path(path_shape, out);
|
||||||
|
}
|
||||||
|
Shape::Rect(rect_shape) => {
|
||||||
|
self.tessellate_rect(&rect_shape, out);
|
||||||
|
}
|
||||||
|
Shape::Text(text_shape) => {
|
||||||
|
if options.debug_paint_text_rects {
|
||||||
|
let rect = text_shape.galley.rect.translate(text_shape.pos.to_vec2());
|
||||||
|
self.tessellate_rect(
|
||||||
|
&RectShape::stroke(rect.expand(0.5), 2.0, (0.5, Color32::GREEN)),
|
||||||
|
out,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
self.tessellate_text(tex_size, text_shape, out);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn tessellate_path(&mut self, path_shape: PathShape, out: &mut Mesh) {
|
||||||
|
if path_shape.points.len() < 2 {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.options.coarse_tessellation_culling
|
||||||
|
&& !path_shape.bounding_rect().intersects(self.clip_rect)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let PathShape {
|
||||||
points,
|
points,
|
||||||
closed,
|
closed,
|
||||||
fill,
|
fill,
|
||||||
stroke,
|
stroke,
|
||||||
} => {
|
} = path_shape;
|
||||||
if points.len() >= 2 {
|
|
||||||
self.scratchpad_path.clear();
|
self.scratchpad_path.clear();
|
||||||
if closed {
|
if closed {
|
||||||
self.scratchpad_path.add_line_loop(&points);
|
self.scratchpad_path.add_line_loop(&points);
|
||||||
|
@ -593,50 +640,18 @@ impl Tessellator {
|
||||||
closed,
|
closed,
|
||||||
"You asked to fill a path that is not closed. That makes no sense."
|
"You asked to fill a path that is not closed. That makes no sense."
|
||||||
);
|
);
|
||||||
self.scratchpad_path.fill(fill, options, out);
|
self.scratchpad_path.fill(fill, self.options, out);
|
||||||
}
|
}
|
||||||
let typ = if closed {
|
let typ = if closed {
|
||||||
PathType::Closed
|
PathType::Closed
|
||||||
} else {
|
} else {
|
||||||
PathType::Open
|
PathType::Open
|
||||||
};
|
};
|
||||||
self.scratchpad_path.stroke(typ, stroke, options, out);
|
self.scratchpad_path.stroke(typ, stroke, self.options, out);
|
||||||
}
|
|
||||||
}
|
|
||||||
Shape::Rect {
|
|
||||||
rect,
|
|
||||||
corner_radius,
|
|
||||||
fill,
|
|
||||||
stroke,
|
|
||||||
} => {
|
|
||||||
let rect = PaintRect {
|
|
||||||
rect,
|
|
||||||
corner_radius,
|
|
||||||
fill,
|
|
||||||
stroke,
|
|
||||||
};
|
|
||||||
self.tessellate_rect(&rect, out);
|
|
||||||
}
|
|
||||||
Shape::Text(text_shape) => {
|
|
||||||
if options.debug_paint_text_rects {
|
|
||||||
self.tessellate_rect(
|
|
||||||
&PaintRect {
|
|
||||||
rect: Rect::from_min_size(text_shape.pos, text_shape.galley.size())
|
|
||||||
.expand(0.5),
|
|
||||||
corner_radius: 2.0,
|
|
||||||
fill: Default::default(),
|
|
||||||
stroke: (0.5, Color32::GREEN).into(),
|
|
||||||
},
|
|
||||||
out,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
self.tessellate_text(tex_size, text_shape, out);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn tessellate_rect(&mut self, rect: &PaintRect, out: &mut Mesh) {
|
pub(crate) fn tessellate_rect(&mut self, rect: &RectShape, out: &mut Mesh) {
|
||||||
let PaintRect {
|
let RectShape {
|
||||||
mut rect,
|
mut rect,
|
||||||
corner_radius,
|
corner_radius,
|
||||||
fill,
|
fill,
|
||||||
|
@ -803,12 +818,11 @@ pub fn tessellate_shapes(
|
||||||
tessellator.clip_rect = Rect::EVERYTHING;
|
tessellator.clip_rect = Rect::EVERYTHING;
|
||||||
tessellator.tessellate_shape(
|
tessellator.tessellate_shape(
|
||||||
tex_size,
|
tex_size,
|
||||||
Shape::Rect {
|
Shape::rect_stroke(
|
||||||
rect: *clip_rect,
|
*clip_rect,
|
||||||
corner_radius: 0.0,
|
0.0,
|
||||||
fill: Default::default(),
|
Stroke::new(2.0, Color32::from_rgb(150, 255, 150)),
|
||||||
stroke: Stroke::new(2.0, Color32::from_rgb(150, 255, 150)),
|
),
|
||||||
},
|
|
||||||
mesh,
|
mesh,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -118,7 +118,6 @@ pub struct FontDefinitions {
|
||||||
///
|
///
|
||||||
/// `epaint` has built-in-default for these,
|
/// `epaint` has built-in-default for these,
|
||||||
/// but you can override them if you like.
|
/// but you can override them if you like.
|
||||||
#[cfg_attr(feature = "persistence", serde(skip))]
|
|
||||||
pub font_data: BTreeMap<String, FontData>,
|
pub font_data: BTreeMap<String, FontData>,
|
||||||
|
|
||||||
/// Which fonts (names) to use for each [`FontFamily`].
|
/// Which fonts (names) to use for each [`FontFamily`].
|
||||||
|
@ -219,7 +218,7 @@ pub struct Fonts {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Fonts {
|
impl Fonts {
|
||||||
pub fn from_definitions(pixels_per_point: f32, definitions: FontDefinitions) -> Self {
|
pub fn new(pixels_per_point: f32, definitions: FontDefinitions) -> Self {
|
||||||
assert!(
|
assert!(
|
||||||
0.0 < pixels_per_point && pixels_per_point < 100.0,
|
0.0 < pixels_per_point && pixels_per_point < 100.0,
|
||||||
"pixels_per_point out of range: {}",
|
"pixels_per_point out of range: {}",
|
||||||
|
@ -278,6 +277,11 @@ impl Fonts {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[deprecated = "Renamed to Fonts::new"]
|
||||||
|
pub fn from_definitions(pixels_per_point: f32, definitions: FontDefinitions) -> Self {
|
||||||
|
Self::new(pixels_per_point, definitions)
|
||||||
|
}
|
||||||
|
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub fn pixels_per_point(&self) -> f32 {
|
pub fn pixels_per_point(&self) -> f32 {
|
||||||
self.pixels_per_point
|
self.pixels_per_point
|
||||||
|
|
|
@ -333,11 +333,13 @@ fn galley_from_rows(fonts: &Fonts, job: Arc<LayoutJob>, mut rows: Vec<Row>) -> G
|
||||||
|
|
||||||
let format_summary = format_summary(&job);
|
let format_summary = format_summary(&job);
|
||||||
|
|
||||||
|
let mut mesh_bounds = Rect::NOTHING;
|
||||||
let mut num_vertices = 0;
|
let mut num_vertices = 0;
|
||||||
let mut num_indices = 0;
|
let mut num_indices = 0;
|
||||||
|
|
||||||
for row in &mut rows {
|
for row in &mut rows {
|
||||||
row.visuals = tessellate_row(fonts, &job, &format_summary, row);
|
row.visuals = tessellate_row(fonts, &job, &format_summary, row);
|
||||||
|
mesh_bounds = mesh_bounds.union(row.visuals.mesh_bounds);
|
||||||
num_vertices += row.visuals.mesh.vertices.len();
|
num_vertices += row.visuals.mesh.vertices.len();
|
||||||
num_indices += row.visuals.mesh.indices.len();
|
num_indices += row.visuals.mesh.indices.len();
|
||||||
}
|
}
|
||||||
|
@ -348,6 +350,7 @@ fn galley_from_rows(fonts: &Fonts, job: Arc<LayoutJob>, mut rows: Vec<Row>) -> G
|
||||||
job,
|
job,
|
||||||
rows,
|
rows,
|
||||||
rect,
|
rect,
|
||||||
|
mesh_bounds,
|
||||||
num_vertices,
|
num_vertices,
|
||||||
num_indices,
|
num_indices,
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,6 +13,7 @@ use emath::*;
|
||||||
///
|
///
|
||||||
/// Pass this to [`Fonts::layout_job]` or [`crate::text::layout`].
|
/// Pass this to [`Fonts::layout_job]` or [`crate::text::layout`].
|
||||||
#[derive(Clone, Debug, PartialEq)]
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
|
#[cfg_attr(feature = "persistence", derive(serde::Deserialize, serde::Serialize))]
|
||||||
pub struct LayoutJob {
|
pub struct LayoutJob {
|
||||||
/// The complete text of this job, referenced by `LayoutSection`.
|
/// The complete text of this job, referenced by `LayoutSection`.
|
||||||
pub text: String, // TODO: Cow<'static, str>
|
pub text: String, // TODO: Cow<'static, str>
|
||||||
|
@ -136,6 +137,7 @@ impl std::hash::Hash for LayoutJob {
|
||||||
// ----------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq)]
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
|
#[cfg_attr(feature = "persistence", derive(serde::Deserialize, serde::Serialize))]
|
||||||
pub struct LayoutSection {
|
pub struct LayoutSection {
|
||||||
/// Can be used for first row indentation.
|
/// Can be used for first row indentation.
|
||||||
pub leading_space: f32,
|
pub leading_space: f32,
|
||||||
|
@ -161,6 +163,7 @@ impl std::hash::Hash for LayoutSection {
|
||||||
// ----------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------
|
||||||
|
|
||||||
#[derive(Copy, Clone, Debug, Hash, PartialEq)]
|
#[derive(Copy, Clone, Debug, Hash, PartialEq)]
|
||||||
|
#[cfg_attr(feature = "persistence", derive(serde::Deserialize, serde::Serialize))]
|
||||||
pub struct TextFormat {
|
pub struct TextFormat {
|
||||||
pub style: TextStyle,
|
pub style: TextStyle,
|
||||||
/// Text color
|
/// Text color
|
||||||
|
@ -225,6 +228,10 @@ pub struct Galley {
|
||||||
/// * [`Align::RIGHT`]: rect.right() == 0.0
|
/// * [`Align::RIGHT`]: rect.right() == 0.0
|
||||||
pub rect: Rect,
|
pub rect: Rect,
|
||||||
|
|
||||||
|
/// Tight bounding box around all the meshes in all the rows.
|
||||||
|
/// Can be used for culling.
|
||||||
|
pub mesh_bounds: Rect,
|
||||||
|
|
||||||
/// Total number of vertices in all the row meshes.
|
/// Total number of vertices in all the row meshes.
|
||||||
pub num_vertices: usize,
|
pub num_vertices: usize,
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue