Merge remote-tracking branch 'egui/master' into dynamic-grid
This commit is contained in:
commit
e373961e21
24 changed files with 319 additions and 68 deletions
|
@ -13,6 +13,8 @@ NOTE: [`epaint`](epaint/CHANGELOG.md), [`eframe`](eframe/CHANGELOG.md), [`egui_w
|
||||||
### Changed 🔧
|
### Changed 🔧
|
||||||
* Renamed `Ui::visible` to `Ui::is_visible`.
|
* Renamed `Ui::visible` to `Ui::is_visible`.
|
||||||
|
|
||||||
|
### Fixed 🐛
|
||||||
|
* Context menu now respects the theme ([#1043](https://github.com/emilk/egui/pull/1043))
|
||||||
|
|
||||||
## 0.16.1 - 2021-12-31 - Add back `CtxRef::begin_frame,end_frame`
|
## 0.16.1 - 2021-12-31 - Add back `CtxRef::begin_frame,end_frame`
|
||||||
|
|
||||||
|
@ -49,7 +51,7 @@ NOTE: [`epaint`](epaint/CHANGELOG.md), [`eframe`](eframe/CHANGELOG.md), [`egui_w
|
||||||
|
|
||||||
### Fixed 🐛
|
### Fixed 🐛
|
||||||
* Fixed `ComboBox` and other popups getting clipped to parent window ([#885](https://github.com/emilk/egui/pull/885)).
|
* Fixed `ComboBox` and other popups getting clipped to parent window ([#885](https://github.com/emilk/egui/pull/885)).
|
||||||
* The color picker is now better att keeping the same hue even when saturation goes to zero ([#886](https://github.com/emilk/egui/pull/886)).
|
* The color picker is now better at keeping the same hue even when saturation goes to zero ([#886](https://github.com/emilk/egui/pull/886)).
|
||||||
|
|
||||||
### Removed 🔥
|
### Removed 🔥
|
||||||
* Removed `egui::math` (use `egui::emath` instead).
|
* Removed `egui::math` (use `egui::emath` instead).
|
||||||
|
|
1
Cargo.lock
generated
1
Cargo.lock
generated
|
@ -969,6 +969,7 @@ dependencies = [
|
||||||
"atomic_refcell",
|
"atomic_refcell",
|
||||||
"bytemuck",
|
"bytemuck",
|
||||||
"cint",
|
"cint",
|
||||||
|
"criterion",
|
||||||
"emath",
|
"emath",
|
||||||
"nohash-hasher",
|
"nohash-hasher",
|
||||||
"parking_lot",
|
"parking_lot",
|
||||||
|
|
|
@ -454,12 +454,10 @@ impl State {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
if cfg!(target_os = "macos") {
|
if cfg!(target_os = "macos") {
|
||||||
// This is still buggy in winit despite
|
delta.x *= -1.0; // until https://github.com/rust-windowing/winit/pull/2105 is merged and released
|
||||||
// https://github.com/rust-windowing/winit/issues/1695 being closed
|
|
||||||
delta.x *= -1.0;
|
|
||||||
}
|
}
|
||||||
if cfg!(target_os = "windows") {
|
if cfg!(target_os = "windows") {
|
||||||
delta.x *= -1.0; // until https://github.com/rust-windowing/winit/pull/2101 is merged
|
delta.x *= -1.0; // until https://github.com/rust-windowing/winit/pull/2101 is merged and released
|
||||||
}
|
}
|
||||||
|
|
||||||
if self.egui_input.modifiers.ctrl || self.egui_input.modifiers.command {
|
if self.egui_input.modifiers.ctrl || self.egui_input.modifiers.command {
|
||||||
|
|
|
@ -117,6 +117,14 @@ impl SidePanel {
|
||||||
/// Can panel be resized by dragging the edge of it?
|
/// Can panel be resized by dragging the edge of it?
|
||||||
///
|
///
|
||||||
/// Default is `true`.
|
/// Default is `true`.
|
||||||
|
///
|
||||||
|
/// If you want your panel to be resizable you also need a widget in it that
|
||||||
|
/// takes up more space as you resize it, such as:
|
||||||
|
/// * Wrapping text ([`Ui::horizontal_wrapped`]).
|
||||||
|
/// * A [`ScrollArea`].
|
||||||
|
/// * A [`Separator`].
|
||||||
|
/// * A [`TextEdit`].
|
||||||
|
/// * …
|
||||||
pub fn resizable(mut self, resizable: bool) -> Self {
|
pub fn resizable(mut self, resizable: bool) -> Self {
|
||||||
self.resizable = resizable;
|
self.resizable = resizable;
|
||||||
self
|
self
|
||||||
|
@ -393,6 +401,14 @@ impl TopBottomPanel {
|
||||||
/// Can panel be resized by dragging the edge of it?
|
/// Can panel be resized by dragging the edge of it?
|
||||||
///
|
///
|
||||||
/// Default is `false`.
|
/// Default is `false`.
|
||||||
|
///
|
||||||
|
/// If you want your panel to be resizable you also need a widget in it that
|
||||||
|
/// takes up more space as you resize it, such as:
|
||||||
|
/// * Wrapping text ([`Ui::horizontal_wrapped`]).
|
||||||
|
/// * A [`ScrollArea`].
|
||||||
|
/// * A [`Separator`].
|
||||||
|
/// * A [`TextEdit`].
|
||||||
|
/// * …
|
||||||
pub fn resizable(mut self, resizable: bool) -> Self {
|
pub fn resizable(mut self, resizable: bool) -> Self {
|
||||||
self.resizable = resizable;
|
self.resizable = resizable;
|
||||||
self
|
self
|
||||||
|
|
|
@ -376,10 +376,10 @@ impl ScrollArea {
|
||||||
/// let text_style = egui::TextStyle::Body;
|
/// let text_style = egui::TextStyle::Body;
|
||||||
/// let row_height = ui.fonts()[text_style].row_height();
|
/// let row_height = ui.fonts()[text_style].row_height();
|
||||||
/// // let row_height = ui.spacing().interact_size.y; // if you are adding buttons instead of labels.
|
/// // let row_height = ui.spacing().interact_size.y; // if you are adding buttons instead of labels.
|
||||||
/// let num_rows = 10_000;
|
/// let total_rows = 10_000;
|
||||||
/// egui::ScrollArea::vertical().show_rows(ui, row_height, num_rows, |ui, row_range| {
|
/// egui::ScrollArea::vertical().show_rows(ui, row_height, total_rows, |ui, row_range| {
|
||||||
/// for row in row_range {
|
/// for row in row_range {
|
||||||
/// let text = format!("Row {}/{}", row + 1, num_rows);
|
/// let text = format!("Row {}/{}", row + 1, total_rows);
|
||||||
/// ui.label(text);
|
/// ui.label(text);
|
||||||
/// }
|
/// }
|
||||||
/// });
|
/// });
|
||||||
|
@ -389,19 +389,19 @@ impl ScrollArea {
|
||||||
self,
|
self,
|
||||||
ui: &mut Ui,
|
ui: &mut Ui,
|
||||||
row_height_sans_spacing: f32,
|
row_height_sans_spacing: f32,
|
||||||
num_rows: usize,
|
total_rows: usize,
|
||||||
add_contents: impl FnOnce(&mut Ui, std::ops::Range<usize>) -> R,
|
add_contents: impl FnOnce(&mut Ui, std::ops::Range<usize>) -> R,
|
||||||
) -> R {
|
) -> R {
|
||||||
let spacing = ui.spacing().item_spacing;
|
let spacing = ui.spacing().item_spacing;
|
||||||
let row_height_with_spacing = row_height_sans_spacing + spacing.y;
|
let row_height_with_spacing = row_height_sans_spacing + spacing.y;
|
||||||
self.show_viewport(ui, |ui, viewport| {
|
self.show_viewport(ui, |ui, viewport| {
|
||||||
ui.set_height((row_height_with_spacing * num_rows as f32 - spacing.y).at_least(0.0));
|
ui.set_height((row_height_with_spacing * total_rows as f32 - spacing.y).at_least(0.0));
|
||||||
|
|
||||||
let min_row = (viewport.min.y / row_height_with_spacing)
|
let min_row = (viewport.min.y / row_height_with_spacing)
|
||||||
.floor()
|
.floor()
|
||||||
.at_least(0.0) as usize;
|
.at_least(0.0) as usize;
|
||||||
let max_row = (viewport.max.y / row_height_with_spacing).ceil() as usize + 1;
|
let max_row = (viewport.max.y / row_height_with_spacing).ceil() as usize + 1;
|
||||||
let max_row = max_row.at_most(num_rows);
|
let max_row = max_row.at_most(total_rows);
|
||||||
|
|
||||||
let y_min = ui.max_rect().top() + min_row as f32 * row_height_with_spacing;
|
let y_min = ui.max_rect().top() + min_row as f32 * row_height_with_spacing;
|
||||||
let y_max = ui.max_rect().top() + max_row as f32 * row_height_with_spacing;
|
let y_max = ui.max_rect().top() + max_row as f32 * row_height_with_spacing;
|
||||||
|
|
|
@ -100,7 +100,7 @@ impl CtxRef {
|
||||||
/// This will modify the internal reference to point to a new generation of [`Context`].
|
/// This will modify the internal reference to point to a new generation of [`Context`].
|
||||||
/// Any old clones of this [`CtxRef`] will refer to the old [`Context`], which will not get new input.
|
/// Any old clones of this [`CtxRef`] will refer to the old [`Context`], which will not get new input.
|
||||||
///
|
///
|
||||||
/// You can alternatively run [`Self::begin_frame`] and [`Self::end_frame`].
|
/// You can alternatively run [`Self::begin_frame`] and [`Context::end_frame`].
|
||||||
///
|
///
|
||||||
/// ``` rust
|
/// ``` rust
|
||||||
/// // One egui context that you keep reusing:
|
/// // One egui context that you keep reusing:
|
||||||
|
|
|
@ -174,6 +174,10 @@ pub enum Event {
|
||||||
PointerGone,
|
PointerGone,
|
||||||
|
|
||||||
/// How many points (logical pixels) the user scrolled.
|
/// How many points (logical pixels) the user scrolled.
|
||||||
|
///
|
||||||
|
/// The direction of the vector indicates how to move the _content_ that is being viewed.
|
||||||
|
/// So if you get positive values, the content being viewed should move to the right and down,
|
||||||
|
/// revealing new things to the left and up.
|
||||||
Scroll(Vec2),
|
Scroll(Vec2),
|
||||||
|
|
||||||
/// Zoom scale factor this frame (e.g. from ctrl-scroll or pinch gesture).
|
/// Zoom scale factor this frame (e.g. from ctrl-scroll or pinch gesture).
|
||||||
|
|
|
@ -214,7 +214,7 @@
|
||||||
//! when you release the panel/window shrinks again.
|
//! when you release the panel/window shrinks again.
|
||||||
//! This is an artifact of immediate mode, and here are some alternatives on how to avoid it:
|
//! This is an artifact of immediate mode, and here are some alternatives on how to avoid it:
|
||||||
//!
|
//!
|
||||||
//! 1. Turn of resizing with [`Window::resizable`], [`SidePanel::resizable`], [`TopBottomPanel::resizable`].
|
//! 1. Turn off resizing with [`Window::resizable`], [`SidePanel::resizable`], [`TopBottomPanel::resizable`].
|
||||||
//! 2. Wrap your panel contents in a [`ScrollArea`], or use [`Window::vscroll`] and [`Window::hscroll`].
|
//! 2. Wrap your panel contents in a [`ScrollArea`], or use [`Window::vscroll`] and [`Window::hscroll`].
|
||||||
//! 3. Use a justified layout:
|
//! 3. Use a justified layout:
|
||||||
//!
|
//!
|
||||||
|
|
|
@ -16,9 +16,8 @@
|
||||||
//! ```
|
//! ```
|
||||||
|
|
||||||
use super::{
|
use super::{
|
||||||
style::{Spacing, WidgetVisuals},
|
style::WidgetVisuals, Align, CtxRef, Id, InnerResponse, PointerState, Pos2, Rect, Response,
|
||||||
Align, CtxRef, Id, InnerResponse, PointerState, Pos2, Rect, Response, Sense, Style, TextStyle,
|
Sense, TextStyle, Ui, Vec2,
|
||||||
Ui, Vec2,
|
|
||||||
};
|
};
|
||||||
use crate::{widgets::*, *};
|
use crate::{widgets::*, *};
|
||||||
use epaint::{mutex::RwLock, Stroke};
|
use epaint::{mutex::RwLock, Stroke};
|
||||||
|
@ -120,7 +119,6 @@ pub(crate) fn menu_ui<'c, R>(
|
||||||
ctx: &CtxRef,
|
ctx: &CtxRef,
|
||||||
menu_id: impl std::hash::Hash,
|
menu_id: impl std::hash::Hash,
|
||||||
menu_state_arc: &Arc<RwLock<MenuState>>,
|
menu_state_arc: &Arc<RwLock<MenuState>>,
|
||||||
mut style: Style,
|
|
||||||
add_contents: impl FnOnce(&mut Ui) -> R + 'c,
|
add_contents: impl FnOnce(&mut Ui) -> R + 'c,
|
||||||
) -> InnerResponse<R> {
|
) -> InnerResponse<R> {
|
||||||
let pos = {
|
let pos = {
|
||||||
|
@ -128,29 +126,34 @@ pub(crate) fn menu_ui<'c, R>(
|
||||||
menu_state.entry_count = 0;
|
menu_state.entry_count = 0;
|
||||||
menu_state.rect.min
|
menu_state.rect.min
|
||||||
};
|
};
|
||||||
// style.visuals.widgets.active.bg_fill = Color32::TRANSPARENT;
|
|
||||||
style.visuals.widgets.active.bg_stroke = Stroke::none();
|
|
||||||
// style.visuals.widgets.hovered.bg_fill = Color32::TRANSPARENT;
|
|
||||||
style.visuals.widgets.hovered.bg_stroke = Stroke::none();
|
|
||||||
style.visuals.widgets.inactive.bg_fill = Color32::TRANSPARENT;
|
|
||||||
style.visuals.widgets.inactive.bg_stroke = Stroke::none();
|
|
||||||
let area = Area::new(menu_id)
|
let area = Area::new(menu_id)
|
||||||
.order(Order::Foreground)
|
.order(Order::Foreground)
|
||||||
.fixed_pos(pos)
|
.fixed_pos(pos)
|
||||||
.interactable(false)
|
.interactable(false)
|
||||||
.drag_bounds(Rect::EVERYTHING);
|
.drag_bounds(Rect::EVERYTHING);
|
||||||
let frame = Frame::menu(&style);
|
|
||||||
let inner_response = area.show(ctx, |ui| {
|
let inner_response = area.show(ctx, |ui| {
|
||||||
frame
|
ui.scope(|ui| {
|
||||||
.show(ui, |ui| {
|
let style = ui.style_mut();
|
||||||
const DEFAULT_MENU_WIDTH: f32 = 150.0; // TODO: add to ui.spacing
|
style.spacing.item_spacing = Vec2::ZERO;
|
||||||
ui.set_max_width(DEFAULT_MENU_WIDTH);
|
style.spacing.button_padding = crate::vec2(2.0, 0.0);
|
||||||
ui.set_style(style);
|
|
||||||
ui.set_menu_state(Some(menu_state_arc.clone()));
|
style.visuals.widgets.active.bg_stroke = Stroke::none();
|
||||||
ui.with_layout(Layout::top_down_justified(Align::LEFT), add_contents)
|
style.visuals.widgets.hovered.bg_stroke = Stroke::none();
|
||||||
.inner
|
style.visuals.widgets.inactive.bg_fill = Color32::TRANSPARENT;
|
||||||
})
|
style.visuals.widgets.inactive.bg_stroke = Stroke::none();
|
||||||
.inner
|
|
||||||
|
Frame::menu(style)
|
||||||
|
.show(ui, |ui| {
|
||||||
|
const DEFAULT_MENU_WIDTH: f32 = 150.0; // TODO: add to ui.spacing
|
||||||
|
ui.set_max_width(DEFAULT_MENU_WIDTH);
|
||||||
|
ui.set_menu_state(Some(menu_state_arc.clone()));
|
||||||
|
ui.with_layout(Layout::top_down_justified(Align::LEFT), add_contents)
|
||||||
|
.inner
|
||||||
|
})
|
||||||
|
.inner
|
||||||
|
})
|
||||||
|
.inner
|
||||||
});
|
});
|
||||||
menu_state_arc.write().rect = inner_response.response.rect;
|
menu_state_arc.write().rect = inner_response.response.rect;
|
||||||
inner_response
|
inner_response
|
||||||
|
@ -522,15 +525,7 @@ impl MenuState {
|
||||||
id: Id,
|
id: Id,
|
||||||
add_contents: impl FnOnce(&mut Ui) -> R,
|
add_contents: impl FnOnce(&mut Ui) -> R,
|
||||||
) -> InnerResponse<R> {
|
) -> InnerResponse<R> {
|
||||||
let style = Style {
|
crate::menu::menu_ui(ctx, id, menu_state, add_contents)
|
||||||
spacing: Spacing {
|
|
||||||
item_spacing: Vec2::ZERO,
|
|
||||||
button_padding: crate::vec2(2.0, 0.0),
|
|
||||||
..Default::default()
|
|
||||||
},
|
|
||||||
..Default::default()
|
|
||||||
};
|
|
||||||
crate::menu::menu_ui(ctx, id, menu_state, style, add_contents)
|
|
||||||
}
|
}
|
||||||
fn show_submenu<R>(
|
fn show_submenu<R>(
|
||||||
&mut self,
|
&mut self,
|
||||||
|
|
|
@ -6,6 +6,9 @@ enum ProgressBarText {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A simple progress bar.
|
/// A simple progress bar.
|
||||||
|
///
|
||||||
|
/// See also: [`crate::Spinner`].
|
||||||
|
#[must_use = "You should put this widget in an ui with `ui.add(widget);`"]
|
||||||
pub struct ProgressBar {
|
pub struct ProgressBar {
|
||||||
progress: f32,
|
progress: f32,
|
||||||
desired_width: Option<f32>,
|
desired_width: Option<f32>,
|
||||||
|
@ -62,10 +65,6 @@ impl Widget for ProgressBar {
|
||||||
|
|
||||||
let animate = animate && progress < 1.0;
|
let animate = animate && progress < 1.0;
|
||||||
|
|
||||||
if animate {
|
|
||||||
ui.ctx().request_repaint();
|
|
||||||
}
|
|
||||||
|
|
||||||
let desired_width =
|
let desired_width =
|
||||||
desired_width.unwrap_or_else(|| ui.available_size_before_wrap().x.at_least(96.0));
|
desired_width.unwrap_or_else(|| ui.available_size_before_wrap().x.at_least(96.0));
|
||||||
let height = ui.spacing().interact_size.y;
|
let height = ui.spacing().interact_size.y;
|
||||||
|
@ -73,6 +72,10 @@ impl Widget for ProgressBar {
|
||||||
ui.allocate_exact_size(vec2(desired_width, height), Sense::hover());
|
ui.allocate_exact_size(vec2(desired_width, height), Sense::hover());
|
||||||
|
|
||||||
if ui.is_rect_visible(response.rect) {
|
if ui.is_rect_visible(response.rect) {
|
||||||
|
if animate {
|
||||||
|
ui.ctx().request_repaint();
|
||||||
|
}
|
||||||
|
|
||||||
let visuals = ui.style().visuals.clone();
|
let visuals = ui.style().visuals.clone();
|
||||||
let corner_radius = outer_rect.height() / 2.0;
|
let corner_radius = outer_rect.height() / 2.0;
|
||||||
ui.painter().rect(
|
ui.painter().rect(
|
||||||
|
|
|
@ -3,6 +3,8 @@ use epaint::{emath::lerp, vec2, Pos2, Shape, Stroke};
|
||||||
use crate::{Response, Sense, Ui, Widget};
|
use crate::{Response, Sense, Ui, Widget};
|
||||||
|
|
||||||
/// A spinner widget used to indicate loading.
|
/// A spinner widget used to indicate loading.
|
||||||
|
///
|
||||||
|
/// See also: [`crate::ProgressBar`].
|
||||||
#[must_use = "You should put this widget in an ui with `ui.add(widget);`"]
|
#[must_use = "You should put this widget in an ui with `ui.add(widget);`"]
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
pub struct Spinner {
|
pub struct Spinner {
|
||||||
|
|
|
@ -44,6 +44,8 @@ use super::{CCursorRange, CursorRange, TextEditOutput, TextEditState};
|
||||||
/// }
|
/// }
|
||||||
/// ```
|
/// ```
|
||||||
///
|
///
|
||||||
|
/// ## Advanced usage
|
||||||
|
/// See [`TextEdit::show`].
|
||||||
#[must_use = "You should put this widget in an ui with `ui.add(widget);`"]
|
#[must_use = "You should put this widget in an ui with `ui.add(widget);`"]
|
||||||
pub struct TextEdit<'t> {
|
pub struct TextEdit<'t> {
|
||||||
text: &'t mut dyn TextBuffer,
|
text: &'t mut dyn TextBuffer,
|
||||||
|
@ -242,6 +244,20 @@ impl<'t> Widget for TextEdit<'t> {
|
||||||
|
|
||||||
impl<'t> TextEdit<'t> {
|
impl<'t> TextEdit<'t> {
|
||||||
/// Show the [`TextEdit`], returning a rich [`TextEditOutput`].
|
/// Show the [`TextEdit`], returning a rich [`TextEditOutput`].
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// # egui::__run_test_ui(|ui| {
|
||||||
|
/// # let mut my_string = String::new();
|
||||||
|
/// let output = egui::TextEdit::singleline(&mut my_string).show(ui);
|
||||||
|
/// if let Some(text_cursor_range) = output.cursor_range {
|
||||||
|
/// use egui::TextBuffer as _;
|
||||||
|
/// let selected_chars = text_cursor_range.as_sorted_char_range();
|
||||||
|
/// let selected_text = my_string.char_range(selected_chars);
|
||||||
|
/// ui.label("Selected text: ");
|
||||||
|
/// ui.monospace(selected_text);
|
||||||
|
/// }
|
||||||
|
/// # });
|
||||||
|
/// ```
|
||||||
pub fn show(self, ui: &mut Ui) -> TextEditOutput {
|
pub fn show(self, ui: &mut Ui) -> TextEditOutput {
|
||||||
let is_mutable = self.text.is_mutable();
|
let is_mutable = self.text.is_mutable();
|
||||||
let frame = self.frame;
|
let frame = self.frame;
|
||||||
|
@ -386,7 +402,8 @@ impl<'t> TextEdit<'t> {
|
||||||
Sense::hover()
|
Sense::hover()
|
||||||
};
|
};
|
||||||
let mut response = ui.interact(rect, id, sense);
|
let mut response = ui.interact(rect, id, sense);
|
||||||
let painter = ui.painter_at(rect);
|
let text_clip_rect = rect;
|
||||||
|
let painter = ui.painter_at(text_clip_rect);
|
||||||
|
|
||||||
if interactive {
|
if interactive {
|
||||||
if let Some(pointer_pos) = ui.input().pointer.interact_pos() {
|
if let Some(pointer_pos) = ui.input().pointer.interact_pos() {
|
||||||
|
@ -593,6 +610,8 @@ impl<'t> TextEdit<'t> {
|
||||||
TextEditOutput {
|
TextEditOutput {
|
||||||
response,
|
response,
|
||||||
galley,
|
galley,
|
||||||
|
text_draw_pos,
|
||||||
|
text_clip_rect,
|
||||||
state,
|
state,
|
||||||
cursor_range,
|
cursor_range,
|
||||||
}
|
}
|
||||||
|
@ -806,7 +825,7 @@ fn paint_cursor_selection(
|
||||||
|
|
||||||
// We paint the cursor selection on top of the text, so make it transparent:
|
// We paint the cursor selection on top of the text, so make it transparent:
|
||||||
let color = ui.visuals().selection.bg_fill.linear_multiply(0.5);
|
let color = ui.visuals().selection.bg_fill.linear_multiply(0.5);
|
||||||
let [min, max] = cursor_range.sorted();
|
let [min, max] = cursor_range.sorted_cursors();
|
||||||
let min = min.rcursor;
|
let min = min.rcursor;
|
||||||
let max = max.rcursor;
|
let max = max.rcursor;
|
||||||
|
|
||||||
|
@ -875,7 +894,7 @@ fn paint_cursor_end(
|
||||||
// ----------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------
|
||||||
|
|
||||||
fn selected_str<'s>(text: &'s dyn TextBuffer, cursor_range: &CursorRange) -> &'s str {
|
fn selected_str<'s>(text: &'s dyn TextBuffer, cursor_range: &CursorRange) -> &'s str {
|
||||||
let [min, max] = cursor_range.sorted();
|
let [min, max] = cursor_range.sorted_cursors();
|
||||||
text.char_range(min.ccursor.index..max.ccursor.index)
|
text.char_range(min.ccursor.index..max.ccursor.index)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -886,7 +905,7 @@ fn insert_text(ccursor: &mut CCursor, text: &mut dyn TextBuffer, text_to_insert:
|
||||||
// ----------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------
|
||||||
|
|
||||||
fn delete_selected(text: &mut dyn TextBuffer, cursor_range: &CursorRange) -> CCursor {
|
fn delete_selected(text: &mut dyn TextBuffer, cursor_range: &CursorRange) -> CCursor {
|
||||||
let [min, max] = cursor_range.sorted();
|
let [min, max] = cursor_range.sorted_cursors();
|
||||||
delete_selected_ccursor_range(text, [min.ccursor, max.ccursor])
|
delete_selected_ccursor_range(text, [min.ccursor, max.ccursor])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -927,7 +946,7 @@ fn delete_paragraph_before_cursor(
|
||||||
galley: &Galley,
|
galley: &Galley,
|
||||||
cursor_range: &CursorRange,
|
cursor_range: &CursorRange,
|
||||||
) -> CCursor {
|
) -> CCursor {
|
||||||
let [min, max] = cursor_range.sorted();
|
let [min, max] = cursor_range.sorted_cursors();
|
||||||
let min = galley.from_pcursor(PCursor {
|
let min = galley.from_pcursor(PCursor {
|
||||||
paragraph: min.pcursor.paragraph,
|
paragraph: min.pcursor.paragraph,
|
||||||
offset: 0,
|
offset: 0,
|
||||||
|
@ -945,7 +964,7 @@ fn delete_paragraph_after_cursor(
|
||||||
galley: &Galley,
|
galley: &Galley,
|
||||||
cursor_range: &CursorRange,
|
cursor_range: &CursorRange,
|
||||||
) -> CCursor {
|
) -> CCursor {
|
||||||
let [min, max] = cursor_range.sorted();
|
let [min, max] = cursor_range.sorted_cursors();
|
||||||
let max = galley.from_pcursor(PCursor {
|
let max = galley.from_pcursor(PCursor {
|
||||||
paragraph: max.pcursor.paragraph,
|
paragraph: max.pcursor.paragraph,
|
||||||
offset: usize::MAX, // end of paragraph
|
offset: usize::MAX, // end of paragraph
|
||||||
|
@ -984,7 +1003,7 @@ fn on_key_press(
|
||||||
};
|
};
|
||||||
Some(CCursorRange::one(ccursor))
|
Some(CCursorRange::one(ccursor))
|
||||||
}
|
}
|
||||||
Key::Delete if !(cfg!(target_os = "windows") && modifiers.shift) => {
|
Key::Delete if !modifiers.shift || !cfg!(target_os = "windows") => {
|
||||||
let ccursor = if modifiers.mac_cmd {
|
let ccursor = if modifiers.mac_cmd {
|
||||||
delete_paragraph_after_cursor(text, galley, cursor_range)
|
delete_paragraph_after_cursor(text, galley, cursor_range)
|
||||||
} else if let Some(cursor) = cursor_range.single() {
|
} else if let Some(cursor) = cursor_range.single() {
|
||||||
|
@ -1031,9 +1050,9 @@ fn on_key_press(
|
||||||
|
|
||||||
Key::ArrowLeft | Key::ArrowRight if modifiers.is_none() && !cursor_range.is_empty() => {
|
Key::ArrowLeft | Key::ArrowRight if modifiers.is_none() && !cursor_range.is_empty() => {
|
||||||
if key == Key::ArrowLeft {
|
if key == Key::ArrowLeft {
|
||||||
*cursor_range = CursorRange::one(cursor_range.sorted()[0]);
|
*cursor_range = CursorRange::one(cursor_range.sorted_cursors()[0]);
|
||||||
} else {
|
} else {
|
||||||
*cursor_range = CursorRange::one(cursor_range.sorted()[1]);
|
*cursor_range = CursorRange::one(cursor_range.sorted_cursors()[1]);
|
||||||
}
|
}
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
|
@ -37,6 +37,15 @@ impl CursorRange {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The range of selected character indices.
|
||||||
|
pub fn as_sorted_char_range(&self) -> std::ops::Range<usize> {
|
||||||
|
let [start, end] = self.sorted_cursors();
|
||||||
|
std::ops::Range {
|
||||||
|
start: start.ccursor.index,
|
||||||
|
end: end.ccursor.index,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// True if the selected range contains no characters.
|
/// True if the selected range contains no characters.
|
||||||
pub fn is_empty(&self) -> bool {
|
pub fn is_empty(&self) -> bool {
|
||||||
self.primary.ccursor == self.secondary.ccursor
|
self.primary.ccursor == self.secondary.ccursor
|
||||||
|
@ -58,8 +67,19 @@ impl CursorRange {
|
||||||
(p.index, p.prefer_next_row) <= (s.index, s.prefer_next_row)
|
(p.index, p.prefer_next_row) <= (s.index, s.prefer_next_row)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn sorted(self) -> Self {
|
||||||
|
if self.is_sorted() {
|
||||||
|
self
|
||||||
|
} else {
|
||||||
|
Self {
|
||||||
|
primary: self.secondary,
|
||||||
|
secondary: self.primary,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// returns the two ends ordered
|
/// returns the two ends ordered
|
||||||
pub fn sorted(&self) -> [Cursor; 2] {
|
pub fn sorted_cursors(&self) -> [Cursor; 2] {
|
||||||
if self.is_sorted() {
|
if self.is_sorted() {
|
||||||
[self.primary, self.secondary]
|
[self.primary, self.secondary]
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -8,9 +8,17 @@ pub struct TextEditOutput {
|
||||||
/// How the text was displayed.
|
/// How the text was displayed.
|
||||||
pub galley: Arc<crate::Galley>,
|
pub galley: Arc<crate::Galley>,
|
||||||
|
|
||||||
/// The state we stored after the run/
|
/// Where the text in [`Self::galley`] ended up on the screen.
|
||||||
|
pub text_draw_pos: crate::Pos2,
|
||||||
|
|
||||||
|
/// The text was clipped to this rectangle when painted.
|
||||||
|
pub text_clip_rect: crate::Rect,
|
||||||
|
|
||||||
|
/// The state we stored after the run.
|
||||||
pub state: super::TextEditState,
|
pub state: super::TextEditState,
|
||||||
|
|
||||||
/// Where the text cursor is.
|
/// Where the text cursor is.
|
||||||
pub cursor_range: Option<super::CursorRange>,
|
pub cursor_range: Option<super::CursorRange>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: add `output.paint` and `output.store` and split out that code from `TextEdit::show`.
|
||||||
|
|
|
@ -30,6 +30,7 @@ impl Default for Demos {
|
||||||
Box::new(super::scrolling::Scrolling::default()),
|
Box::new(super::scrolling::Scrolling::default()),
|
||||||
Box::new(super::sliders::Sliders::default()),
|
Box::new(super::sliders::Sliders::default()),
|
||||||
Box::new(super::table_demo::TableDemo::default()),
|
Box::new(super::table_demo::TableDemo::default()),
|
||||||
|
Box::new(super::text_edit::TextEdit::default()),
|
||||||
Box::new(super::widget_gallery::WidgetGallery::default()),
|
Box::new(super::widget_gallery::WidgetGallery::default()),
|
||||||
Box::new(super::window_options::WindowOptions::default()),
|
Box::new(super::window_options::WindowOptions::default()),
|
||||||
Box::new(super::tests::WindowResizeTest::default()),
|
Box::new(super::tests::WindowResizeTest::default()),
|
||||||
|
|
|
@ -23,6 +23,7 @@ pub mod scrolling;
|
||||||
pub mod sliders;
|
pub mod sliders;
|
||||||
pub mod table_demo;
|
pub mod table_demo;
|
||||||
pub mod tests;
|
pub mod tests;
|
||||||
|
pub mod text_edit;
|
||||||
pub mod toggle_switch;
|
pub mod toggle_switch;
|
||||||
pub mod widget_gallery;
|
pub mod widget_gallery;
|
||||||
pub mod window_options;
|
pub mod window_options;
|
||||||
|
|
83
egui_demo_lib/src/apps/demo/text_edit.rs
Normal file
83
egui_demo_lib/src/apps/demo/text_edit.rs
Normal file
|
@ -0,0 +1,83 @@
|
||||||
|
/// Showcase [`TextEdit`].
|
||||||
|
#[derive(PartialEq)]
|
||||||
|
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
|
||||||
|
#[cfg_attr(feature = "serde", serde(default))]
|
||||||
|
pub struct TextEdit {
|
||||||
|
pub text: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for TextEdit {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
text: "Edit this text".to_owned(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl super::Demo for TextEdit {
|
||||||
|
fn name(&self) -> &'static str {
|
||||||
|
"🖹 TextEdit"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn show(&mut self, ctx: &egui::CtxRef, open: &mut bool) {
|
||||||
|
egui::Window::new(self.name())
|
||||||
|
.open(open)
|
||||||
|
.resizable(false)
|
||||||
|
.show(ctx, |ui| {
|
||||||
|
use super::View as _;
|
||||||
|
self.ui(ui);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl super::View for TextEdit {
|
||||||
|
fn ui(&mut self, ui: &mut egui::Ui) {
|
||||||
|
let Self { text } = self;
|
||||||
|
|
||||||
|
ui.horizontal(|ui| {
|
||||||
|
ui.spacing_mut().item_spacing.x = 0.0;
|
||||||
|
ui.label("Advanced usage of ");
|
||||||
|
ui.code("TextEdit");
|
||||||
|
ui.label(".");
|
||||||
|
});
|
||||||
|
|
||||||
|
let output = egui::TextEdit::multiline(text)
|
||||||
|
.hint_text("Type something!")
|
||||||
|
.show(ui);
|
||||||
|
|
||||||
|
ui.horizontal(|ui| {
|
||||||
|
ui.spacing_mut().item_spacing.x = 0.0;
|
||||||
|
ui.label("Selected text: ");
|
||||||
|
if let Some(text_cursor_range) = output.cursor_range {
|
||||||
|
use egui::TextBuffer as _;
|
||||||
|
let selected_chars = text_cursor_range.as_sorted_char_range();
|
||||||
|
let selected_text = text.char_range(selected_chars);
|
||||||
|
ui.code(selected_text);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
let anything_selected = output
|
||||||
|
.cursor_range
|
||||||
|
.map_or(false, |cursor| !cursor.is_empty());
|
||||||
|
|
||||||
|
ui.add_enabled(
|
||||||
|
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 let Some(text_cursor_range) = output.cursor_range {
|
||||||
|
use egui::TextBuffer as _;
|
||||||
|
let selected_chars = text_cursor_range.as_sorted_char_range();
|
||||||
|
let selected_text = text.char_range(selected_chars.clone());
|
||||||
|
let upper_case = selected_text.to_uppercase();
|
||||||
|
let new_text = if selected_text == upper_case {
|
||||||
|
selected_text.to_lowercase()
|
||||||
|
} else {
|
||||||
|
upper_case
|
||||||
|
};
|
||||||
|
text.delete_char_range(selected_chars.clone());
|
||||||
|
text.insert_text(&new_text, selected_chars.start);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -3,7 +3,7 @@ All notable changes to the `egui_glow` integration will be noted in this file.
|
||||||
|
|
||||||
|
|
||||||
## Unreleased
|
## Unreleased
|
||||||
|
* Added `set_texture_filter` method to `Painter` ((#1041)[https://github.com/emilk/egui/pull/1041]).
|
||||||
|
|
||||||
## 0.16.0 - 2021-12-29
|
## 0.16.0 - 2021-12-29
|
||||||
* Made winit/glutin an optional dependency ([#868](https://github.com/emilk/egui/pull/868)).
|
* Made winit/glutin an optional dependency ([#868](https://github.com/emilk/egui/pull/868)).
|
||||||
|
|
|
@ -2,10 +2,13 @@
|
||||||
use glow::HasContext;
|
use glow::HasContext;
|
||||||
use std::option::Option::Some;
|
use std::option::Option::Some;
|
||||||
|
|
||||||
|
use crate::painter::TextureFilter;
|
||||||
|
|
||||||
pub(crate) fn srgbtexture2d(
|
pub(crate) fn srgbtexture2d(
|
||||||
gl: &glow::Context,
|
gl: &glow::Context,
|
||||||
is_webgl_1: bool,
|
is_webgl_1: bool,
|
||||||
srgb_support: bool,
|
srgb_support: bool,
|
||||||
|
texture_filter: TextureFilter,
|
||||||
data: &[u8],
|
data: &[u8],
|
||||||
w: usize,
|
w: usize,
|
||||||
h: usize,
|
h: usize,
|
||||||
|
@ -20,12 +23,12 @@ pub(crate) fn srgbtexture2d(
|
||||||
gl.tex_parameter_i32(
|
gl.tex_parameter_i32(
|
||||||
glow::TEXTURE_2D,
|
glow::TEXTURE_2D,
|
||||||
glow::TEXTURE_MAG_FILTER,
|
glow::TEXTURE_MAG_FILTER,
|
||||||
glow::LINEAR as i32,
|
texture_filter.glow_code() as i32,
|
||||||
);
|
);
|
||||||
gl.tex_parameter_i32(
|
gl.tex_parameter_i32(
|
||||||
glow::TEXTURE_2D,
|
glow::TEXTURE_2D,
|
||||||
glow::TEXTURE_MIN_FILTER,
|
glow::TEXTURE_MIN_FILTER,
|
||||||
glow::LINEAR as i32,
|
texture_filter.glow_code() as i32,
|
||||||
);
|
);
|
||||||
gl.tex_parameter_i32(
|
gl.tex_parameter_i32(
|
||||||
glow::TEXTURE_2D,
|
glow::TEXTURE_2D,
|
||||||
|
|
|
@ -35,6 +35,8 @@ pub struct Painter {
|
||||||
is_embedded: bool,
|
is_embedded: bool,
|
||||||
vertex_array: crate::misc_util::VAO,
|
vertex_array: crate::misc_util::VAO,
|
||||||
srgb_support: bool,
|
srgb_support: bool,
|
||||||
|
/// The filter used for subsequent textures.
|
||||||
|
texture_filter: TextureFilter,
|
||||||
post_process: Option<PostProcess>,
|
post_process: Option<PostProcess>,
|
||||||
vertex_buffer: glow::Buffer,
|
vertex_buffer: glow::Buffer,
|
||||||
element_array_buffer: glow::Buffer,
|
element_array_buffer: glow::Buffer,
|
||||||
|
@ -52,6 +54,27 @@ pub struct Painter {
|
||||||
destroyed: bool,
|
destroyed: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Copy, Clone)]
|
||||||
|
pub enum TextureFilter {
|
||||||
|
Linear,
|
||||||
|
Nearest,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for TextureFilter {
|
||||||
|
fn default() -> Self {
|
||||||
|
TextureFilter::Linear
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TextureFilter {
|
||||||
|
pub(crate) fn glow_code(&self) -> u32 {
|
||||||
|
match self {
|
||||||
|
TextureFilter::Linear => glow::LINEAR,
|
||||||
|
TextureFilter::Nearest => glow::NEAREST,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Painter {
|
impl Painter {
|
||||||
/// Create painter.
|
/// Create painter.
|
||||||
///
|
///
|
||||||
|
@ -190,6 +213,7 @@ impl Painter {
|
||||||
is_embedded: matches!(shader_version, ShaderVersion::Es100 | ShaderVersion::Es300),
|
is_embedded: matches!(shader_version, ShaderVersion::Es100 | ShaderVersion::Es300),
|
||||||
vertex_array,
|
vertex_array,
|
||||||
srgb_support,
|
srgb_support,
|
||||||
|
texture_filter: Default::default(),
|
||||||
post_process,
|
post_process,
|
||||||
vertex_buffer,
|
vertex_buffer,
|
||||||
element_array_buffer,
|
element_array_buffer,
|
||||||
|
@ -224,6 +248,7 @@ impl Painter {
|
||||||
gl,
|
gl,
|
||||||
self.is_webgl_1,
|
self.is_webgl_1,
|
||||||
self.srgb_support,
|
self.srgb_support,
|
||||||
|
self.texture_filter,
|
||||||
&pixels,
|
&pixels,
|
||||||
font_image.width,
|
font_image.width,
|
||||||
font_image.height,
|
font_image.height,
|
||||||
|
@ -387,6 +412,12 @@ impl Painter {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Set the filter to be used for any subsequent textures loaded via
|
||||||
|
// [`Self::set_texture`].
|
||||||
|
pub fn set_texture_filter(&mut self, texture_filter: TextureFilter) {
|
||||||
|
self.texture_filter = texture_filter;
|
||||||
|
}
|
||||||
|
|
||||||
// ------------------------------------------------------------------------
|
// ------------------------------------------------------------------------
|
||||||
|
|
||||||
#[cfg(feature = "epi")]
|
#[cfg(feature = "epi")]
|
||||||
|
@ -410,6 +441,7 @@ impl Painter {
|
||||||
gl,
|
gl,
|
||||||
self.is_webgl_1,
|
self.is_webgl_1,
|
||||||
self.srgb_support,
|
self.srgb_support,
|
||||||
|
self.texture_filter,
|
||||||
&pixels,
|
&pixels,
|
||||||
image.size[0],
|
image.size[0],
|
||||||
image.size[1],
|
image.size[1],
|
||||||
|
|
|
@ -5,6 +5,7 @@ All notable changes to the epaint crate will be documented in this file.
|
||||||
|
|
||||||
## Unreleased
|
## Unreleased
|
||||||
|
|
||||||
|
* Added `Shape::dashed_line_many` ([#1027](https://github.com/emilk/egui/pull/1027)).
|
||||||
|
|
||||||
## 0.16.0 - 2021-12-29
|
## 0.16.0 - 2021-12-29
|
||||||
* Anti-alias path ends ([#893](https://github.com/emilk/egui/pull/893)).
|
* Anti-alias path ends ([#893](https://github.com/emilk/egui/pull/893)).
|
||||||
|
|
|
@ -63,3 +63,10 @@ single_threaded = ["atomic_refcell"]
|
||||||
# Only needed if you plan to use the same fonts from multiple threads.
|
# Only needed if you plan to use the same fonts from multiple threads.
|
||||||
# It comes with a minor performance impact.
|
# It comes with a minor performance impact.
|
||||||
multi_threaded = ["parking_lot"]
|
multi_threaded = ["parking_lot"]
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
criterion = { version = "0.3", default-features = false }
|
||||||
|
|
||||||
|
[[bench]]
|
||||||
|
name = "benchmark"
|
||||||
|
harness = false
|
||||||
|
|
43
epaint/benches/benchmark.rs
Normal file
43
epaint/benches/benchmark.rs
Normal file
|
@ -0,0 +1,43 @@
|
||||||
|
use criterion::{black_box, criterion_group, criterion_main, Criterion};
|
||||||
|
|
||||||
|
use epaint::{pos2, Color32, Shape, Stroke};
|
||||||
|
|
||||||
|
fn single_dashed_lines(c: &mut Criterion) {
|
||||||
|
c.bench_function("single_dashed_lines", move |b| {
|
||||||
|
b.iter(|| {
|
||||||
|
let mut v = Vec::new();
|
||||||
|
|
||||||
|
let line = [pos2(0.0, 0.0), pos2(50.0, 0.0), pos2(100.0, 1.0)];
|
||||||
|
|
||||||
|
for _ in 0..100 {
|
||||||
|
v.extend(Shape::dashed_line(
|
||||||
|
&line,
|
||||||
|
Stroke::new(1.5, Color32::RED),
|
||||||
|
10.0,
|
||||||
|
2.5,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
black_box(v);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
fn many_dashed_lines(c: &mut Criterion) {
|
||||||
|
c.bench_function("many_dashed_lines", move |b| {
|
||||||
|
b.iter(|| {
|
||||||
|
let mut v = Vec::new();
|
||||||
|
|
||||||
|
let line = [pos2(0.0, 0.0), pos2(50.0, 0.0), pos2(100.0, 1.0)];
|
||||||
|
|
||||||
|
for _ in 0..100 {
|
||||||
|
Shape::dashed_line_many(&line, Stroke::new(1.5, Color32::RED), 10.0, 2.5, &mut v);
|
||||||
|
}
|
||||||
|
|
||||||
|
black_box(v);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
criterion_group!(benches, single_dashed_lines, many_dashed_lines);
|
||||||
|
criterion_main!(benches);
|
|
@ -75,6 +75,18 @@ impl Shape {
|
||||||
shapes
|
shapes
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Turn a line into dashes. If you need to create many dashed lines use this instead of
|
||||||
|
/// [`Self::dashed_line`]
|
||||||
|
pub fn dashed_line_many(
|
||||||
|
points: &[Pos2],
|
||||||
|
stroke: impl Into<Stroke>,
|
||||||
|
dash_length: f32,
|
||||||
|
gap_length: f32,
|
||||||
|
shapes: &mut Vec<Shape>,
|
||||||
|
) {
|
||||||
|
dashes_from_line(points, stroke.into(), dash_length, gap_length, shapes);
|
||||||
|
}
|
||||||
|
|
||||||
/// A convex polygon with a fill and optional stroke.
|
/// A convex polygon with a fill and optional stroke.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn convex_polygon(
|
pub fn convex_polygon(
|
||||||
|
@ -425,27 +437,27 @@ fn dashes_from_line(
|
||||||
let end = window[1];
|
let end = window[1];
|
||||||
let vector = end - start;
|
let vector = end - start;
|
||||||
let segment_length = vector.length();
|
let segment_length = vector.length();
|
||||||
|
|
||||||
|
let mut start_point = start;
|
||||||
while position_on_segment < segment_length {
|
while position_on_segment < segment_length {
|
||||||
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(PathShape { points, .. }) = shapes.last_mut().unwrap() {
|
shapes.push(Shape::line_segment([start_point, new_point], stroke));
|
||||||
points.push(new_point);
|
|
||||||
}
|
|
||||||
position_on_segment += gap_length;
|
position_on_segment += gap_length;
|
||||||
} else {
|
} else {
|
||||||
// Start a new dash.
|
// Start a new dash.
|
||||||
shapes.push(Shape::line(vec![new_point], stroke));
|
start_point = new_point;
|
||||||
position_on_segment += dash_length;
|
position_on_segment += dash_length;
|
||||||
}
|
}
|
||||||
drawing_dash = !drawing_dash;
|
drawing_dash = !drawing_dash;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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(PathShape { points, .. }) = shapes.last_mut().unwrap() {
|
shapes.push(Shape::line_segment([start_point, end], stroke));
|
||||||
points.push(end);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
position_on_segment -= segment_length;
|
position_on_segment -= segment_length;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue