1
0
mirror of https://github.com/emilk/egui.git synced 2026-06-28 07:23:13 -04:00

Merge branch 'master' into wgpu-0.17

This commit is contained in:
Emil Ernerfeldt
2023-08-11 16:15:25 +02:00
143 changed files with 1107 additions and 676 deletions

View File

@@ -2,7 +2,8 @@
All notable changes to the `ecolor` crate will be noted in this file.
## Unreleased
This file is updated upon each release.
Changes since the last release can be found by running the `scripts/generate_changelog.py` script.
## 0.22.0 - 2023-05-23

View File

@@ -7,7 +7,7 @@ authors = [
]
description = "Color structs and color conversion utilities"
edition = "2021"
rust-version = "1.65"
rust-version = "1.67"
homepage = "https://github.com/emilk/egui"
license = "MIT OR Apache-2.0"
readme = "README.md"

View File

@@ -3,9 +3,9 @@ All notable changes to the `eframe` crate.
NOTE: [`egui-winit`](../egui-winit/CHANGELOG.md), [`egui_glium`](../egui_glium/CHANGELOG.md), [`egui_glow`](../egui_glow/CHANGELOG.md),and [`egui-wgpu`](../egui-wgpu/CHANGELOG.md) have their own changelogs!
This file is updated upon each release.
Changes since the last release can be found by running the `scripts/generate_changelog.py` script.
## Unreleased
* Expose raw window and display handles in `CreationContext` and `Frame`
## 0.22.0 - 2023-05-23
* Fix: `request_repaint_after` works even when called from background thread [#2939](https://github.com/emilk/egui/pull/2939)

View File

@@ -4,7 +4,7 @@ version = "0.22.0"
authors = ["Emil Ernerfeldt <emil.ernerfeldt@gmail.com>"]
description = "egui framework - write GUI apps that compiles to web and/or natively"
edition = "2021"
rust-version = "1.65"
rust-version = "1.67"
homepage = "https://github.com/emilk/egui/tree/master/crates/eframe"
license = "MIT OR Apache-2.0"
readme = "README.md"
@@ -27,7 +27,14 @@ targets = ["x86_64-unknown-linux-gnu", "wasm32-unknown-unknown"]
[features]
default = ["accesskit", "default_fonts", "glow"]
default = [
"accesskit",
"default_fonts",
"glow",
"wayland",
"winit/default",
"x11",
]
## Enable platform accessibility API implementations through [AccessKit](https://accesskit.dev/).
accesskit = ["egui/accesskit", "egui-winit/accesskit"]
@@ -42,6 +49,9 @@ glow = ["dep:glow", "dep:egui_glow", "dep:glutin", "dep:glutin-winit"]
## Enables wayland support and fixes clipboard issue.
wayland = ["egui-winit/wayland"]
## Enables compiling for x11.
x11 = ["egui-winit/x11"]
## Enable saving app state to disk.
persistence = [
"directories-next",
@@ -109,7 +119,7 @@ image = { version = "0.24", default-features = false, features = [
"png",
] } # Needed for app icon
raw-window-handle = { version = "0.5.0" }
winit = "0.28.1"
winit = { version = "0.28.1", default-features = false }
# optional native:
directories-next = { version = "2", optional = true }

View File

@@ -431,7 +431,7 @@ pub struct NativeOptions {
/// will be used instead.
///
/// ### On Wayland
/// On Wauland this sets the Application ID for the window.
/// On Wayland this sets the Application ID for the window.
///
/// The application ID is used in several places of the compositor, e.g. for
/// grouping windows of the same application. It is also important for

View File

@@ -7,7 +7,7 @@
//! To learn how to set up `eframe` for web and native, go to <https://github.com/emilk/eframe_template/> and follow the instructions there!
//!
//! In short, you implement [`App`] (especially [`App::update`]) and then
//! call [`crate::run_native`] from your `main.rs`, and/or call `eframe::start_web` from your `lib.rs`.
//! call [`crate::run_native`] from your `main.rs`, and/or use `eframe::WebRunner` from your `lib.rs`.
//!
//! ## Usage, native:
//! ``` no_run
@@ -272,6 +272,7 @@ pub fn run_simple_native(
struct SimpleApp<U> {
update_fun: U,
}
impl<U: FnMut(&egui::Context, &mut Frame)> App for SimpleApp<U> {
fn update(&mut self, ctx: &egui::Context, frame: &mut Frame) {
(self.update_fun)(ctx, frame);

View File

@@ -121,9 +121,12 @@ pub fn window_builder<E>(
}
#[cfg(all(feature = "wayland", target_os = "linux"))]
if let Some(app_id) = &native_options.app_id {
{
use winit::platform::wayland::WindowBuilderExtWayland as _;
window_builder = window_builder.with_name(app_id, "");
match &native_options.app_id {
Some(app_id) => window_builder = window_builder.with_name(app_id, ""),
None => window_builder = window_builder.with_name(title, ""),
}
}
if let Some(min_size) = *min_window_size {
@@ -332,8 +335,10 @@ pub struct EpiIntegration {
pub egui_ctx: egui::Context,
pending_full_output: egui::FullOutput,
egui_winit: egui_winit::State,
/// When set, it is time to close the native window.
close: bool,
can_drag_window: bool,
window_state: WindowState,
follow_system_theme: bool,
@@ -562,24 +567,26 @@ impl EpiIntegration {
pub fn maybe_autosave(&mut self, app: &mut dyn epi::App, window: &winit::window::Window) {
let now = std::time::Instant::now();
if now - self.last_auto_save > app.auto_save_interval() {
self.save(app, window);
self.save(app, Some(window));
self.last_auto_save = now;
}
}
#[allow(clippy::unused_self)]
pub fn save(&mut self, _app: &mut dyn epi::App, _window: &winit::window::Window) {
pub fn save(&mut self, _app: &mut dyn epi::App, _window: Option<&winit::window::Window>) {
#[cfg(feature = "persistence")]
if let Some(storage) = self.frame.storage_mut() {
crate::profile_function!();
if _app.persist_native_window() {
crate::profile_scope!("native_window");
epi::set_value(
storage,
STORAGE_WINDOW_KEY,
&WindowSettings::from_display(_window),
);
if let Some(window) = _window {
if _app.persist_native_window() {
crate::profile_scope!("native_window");
epi::set_value(
storage,
STORAGE_WINDOW_KEY,
&WindowSettings::from_display(window),
);
}
}
if _app.persist_egui_memory() {
crate::profile_scope!("egui_memory");

View File

@@ -80,14 +80,45 @@ impl crate::Storage for FileStorage {
join_handle.join().ok();
}
let join_handle = std::thread::spawn(move || {
let file = std::fs::File::create(&file_path).unwrap();
let config = Default::default();
ron::ser::to_writer_pretty(file, &kv, config).unwrap();
log::trace!("Persisted to {:?}", file_path);
});
match std::thread::Builder::new()
.name("eframe_persist".to_owned())
.spawn(move || {
save_to_disk(&file_path, &kv);
}) {
Ok(join_handle) => {
self.last_save_join_handle = Some(join_handle);
}
Err(err) => {
log::warn!("Failed to spawn thread to save app state: {err}");
}
}
}
}
}
self.last_save_join_handle = Some(join_handle);
fn save_to_disk(file_path: &PathBuf, kv: &HashMap<String, String>) {
crate::profile_function!();
if let Some(parent_dir) = file_path.parent() {
if !parent_dir.exists() {
if let Err(err) = std::fs::create_dir_all(parent_dir) {
log::warn!("Failed to create directory {parent_dir:?}: {err}");
}
}
}
match std::fs::File::create(file_path) {
Ok(file) => {
let config = Default::default();
if let Err(err) = ron::ser::to_writer_pretty(file, &kv, config) {
log::warn!("Failed to serialize app state: {err}");
} else {
log::trace!("Persisted to {:?}", file_path);
}
}
Err(err) => {
log::warn!("Failed to create file {file_path:?}: {err}");
}
}
}

View File

@@ -22,6 +22,7 @@ use super::epi_integration::{self, EpiIntegration};
pub enum UserEvent {
RequestRepaint {
when: Instant,
/// What the frame number was when the repaint was _requested_.
frame_nr: u64,
},
@@ -144,11 +145,13 @@ fn run_and_return(
// Platform-dependent event handlers to workaround a winit bug
// See: https://github.com/rust-windowing/winit/issues/987
// See: https://github.com/rust-windowing/winit/issues/1619
winit::event::Event::RedrawEventsCleared if cfg!(windows) => {
#[cfg(windows)]
winit::event::Event::RedrawEventsCleared => {
next_repaint_time = extremely_far_future();
winit_app.run_ui_and_paint()
}
winit::event::Event::RedrawRequested(_) if !cfg!(windows) => {
#[cfg(not(windows))]
winit::event::Event::RedrawRequested(_) => {
next_repaint_time = extremely_far_future();
winit_app.run_ui_and_paint()
}
@@ -705,7 +708,7 @@ mod glow_integration {
let painter =
egui_glow::Painter::new(gl.clone(), "", self.native_options.shader_version)
.unwrap_or_else(|error| panic!("some OpenGL error occurred {}\n", error));
.unwrap_or_else(|err| panic!("An OpenGL error occurred: {err}\n"));
let system_theme = system_theme(gl_window.window(), &self.native_options);
let mut integration = epi_integration::EpiIntegration::new(
@@ -799,7 +802,7 @@ mod glow_integration {
if let Some(mut running) = self.running.take() {
running
.integration
.save(running.app.as_mut(), running.gl_window.window());
.save(running.app.as_mut(), running.gl_window.window.as_ref());
running.app.on_exit(Some(&running.gl));
running.painter.destroy();
}
@@ -1258,9 +1261,9 @@ mod wgpu_integration {
fn save_and_destroy(&mut self) {
if let Some(mut running) = self.running.take() {
if let Some(window) = &self.window {
running.integration.save(running.app.as_mut(), window);
}
running
.integration
.save(running.app.as_mut(), self.window.as_ref());
#[cfg(feature = "glow")]
running.app.on_exit(None);

View File

@@ -104,7 +104,7 @@ pub fn canvas_element(canvas_id: &str) -> Option<web_sys::HtmlCanvasElement> {
pub fn canvas_element_or_die(canvas_id: &str) -> web_sys::HtmlCanvasElement {
canvas_element(canvas_id)
.unwrap_or_else(|| panic!("Failed to find canvas with id {:?}", canvas_id))
.unwrap_or_else(|| panic!("Failed to find canvas with id {canvas_id:?}"))
}
fn canvas_origin(canvas_id: &str) -> egui::Pos2 {

View File

@@ -104,8 +104,7 @@ pub fn install_text_agent(runner_ref: &WebRunner) -> Result<(), JsValue> {
runner_ref.add_event_listener(&input, "focusout", move |_event: web_sys::MouseEvent, _| {
// Delay 10 ms, and focus again.
let func = js_sys::Function::new_no_args(&format!(
"document.getElementById('{}').focus()",
AGENT_ID
"document.getElementById('{AGENT_ID}').focus()"
));
window
.set_timeout_with_callback_and_timeout_and_arguments_0(&func, 10)
@@ -221,8 +220,8 @@ pub fn move_text_cursor(cursor: Option<egui::Pos2>, canvas_id: &str) -> Option<(
let x = (x - canvas.offset_width() as f32 / 2.0)
.min(canvas.client_width() as f32 - bounding_rect.width() as f32);
style.set_property("position", "absolute").ok()?;
style.set_property("top", &format!("{}px", y)).ok()?;
style.set_property("left", &format!("{}px", x)).ok()
style.set_property("top", &format!("{y}px")).ok()?;
style.set_property("left", &format!("{x}px")).ok()
})
} else {
style.set_property("position", "absolute").ok()?;

View File

@@ -27,7 +27,7 @@ impl WebPainterGlow {
let gl = std::sync::Arc::new(gl);
let painter = egui_glow::Painter::new(gl, shader_prefix, None)
.map_err(|error| format!("Error starting glow painter: {}", error))?;
.map_err(|err| format!("Error starting glow painter: {err}"))?;
Ok(Self {
canvas,

View File

@@ -87,8 +87,7 @@ impl WebPainterWgpu {
} else {
// Workaround for https://github.com/gfx-rs/wgpu/issues/3710:
// Don't use `create_surface_from_canvas`, but `create_surface` instead!
let raw_window =
EguiWebWindow(egui::util::hash(&format!("egui on wgpu {canvas_id}")) as u32);
let raw_window = EguiWebWindow(egui::util::hash(("egui on wgpu", canvas_id)) as u32);
canvas.set_attribute("data-raw-handle", &raw_window.0.to_string());
#[allow(unsafe_code)]

View File

@@ -2,8 +2,8 @@
All notable changes to the `egui-wgpu` integration will be noted in this file.
## Unreleased
* Fix panic on wgpu GL backend due to new screenshot capability ([#3068](https://github.com/emilk/egui/issues/3068), [#3078](https://github.com/emilk/egui/pull/3078)
This file is updated upon each release.
Changes since the last release can be found by running the `scripts/generate_changelog.py` script.
## 0.22.0 - 2023-05-23

View File

@@ -8,7 +8,7 @@ authors = [
"Emil Ernerfeldt <emil.ernerfeldt@gmail.com>",
]
edition = "2021"
rust-version = "1.65"
rust-version = "1.67"
homepage = "https://github.com/emilk/egui/tree/master/crates/egui-wgpu"
license = "MIT OR Apache-2.0"
readme = "README.md"
@@ -50,7 +50,7 @@ wgpu.workspace = true
## Enable this when generating docs.
document-features = { version = "0.2", optional = true }
winit = { version = "0.28", optional = true }
winit = { version = "0.28", default-features = false, optional = true }
# Native:
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]

View File

@@ -560,7 +560,7 @@ impl Renderer {
} else {
// allocate a new texture
// Use same label for all resources associated with this texture id (no point in retyping the type)
let label_str = format!("egui_texid_{:?}", id);
let label_str = format!("egui_texid_{id:?}");
let label = Some(label_str.as_str());
let texture = device.create_texture(&wgpu::TextureDescriptor {
label,
@@ -904,8 +904,7 @@ fn create_sampler(
};
device.create_sampler(&wgpu::SamplerDescriptor {
label: Some(&format!(
"egui sampler (mag: {:?}, min {:?})",
mag_filter, min_filter
"egui sampler (mag: {mag_filter:?}, min {min_filter:?})"
)),
mag_filter,
min_filter,

View File

@@ -279,7 +279,7 @@ impl Painter {
depth_or_array_layers: 1,
},
mip_level_count: 1,
sample_count: 1,
sample_count: self.msaa_samples,
dimension: wgpu::TextureDimension::D2,
format: depth_format,
usage: wgpu::TextureUsages::RENDER_ATTACHMENT

View File

@@ -1,8 +1,8 @@
# Changelog for egui-winit
All notable changes to the `egui-winit` integration will be noted in this file.
## Unreleased
This file is updated upon each release.
Changes since the last release can be found by running the `scripts/generate_changelog.py` script.
## 0.22.0 - 2023-05-23

View File

@@ -4,7 +4,7 @@ version = "0.22.0"
authors = ["Emil Ernerfeldt <emil.ernerfeldt@gmail.com>"]
description = "Bindings for using egui with winit"
edition = "2021"
rust-version = "1.65"
rust-version = "1.67"
homepage = "https://github.com/emilk/egui/tree/master/crates/egui-winit"
license = "MIT OR Apache-2.0"
readme = "README.md"
@@ -18,7 +18,7 @@ all-features = true
[features]
default = ["clipboard", "links", "wayland", "winit/default"]
default = ["clipboard", "links", "wayland", "winit/default", "x11"]
## Enable platform accessibility API implementations through [AccessKit](https://accesskit.dev/).
accesskit = ["accesskit_winit", "egui/accesskit"]
@@ -42,6 +42,9 @@ serde = ["egui/serde", "dep:serde"]
## Enables Wayland support.
wayland = ["winit/wayland"]
## Enables compiling for x11.
x11 = ["winit/x11"]
# Allow crates to choose an android-activity backend via Winit
# - It's important that most applications should not have to depend on android-activity directly, and can
# rely on Winit to pull in a suitable version (unlike most Rust crates, any version conflicts won't link)

View File

@@ -3,7 +3,7 @@ use raw_window_handle::HasRawDisplayHandle;
/// Handles interfacing with the OS clipboard.
///
/// If the "clipboard" feature is off, or we cannot connect to the OS clipboard,
/// then a fallback clipboard that just works works within the same app is used instead.
/// then a fallback clipboard that just works within the same app is used instead.
pub struct Clipboard {
#[cfg(all(feature = "arboard", not(target_os = "android")))]
arboard: Option<arboard::Clipboard>,

View File

@@ -307,6 +307,7 @@ impl State {
}
WindowEvent::KeyboardInput { input, .. } => {
self.on_keyboard_input(input);
// When pressing the Tab key, egui focuses the first focusable element, hence Tab always consumes.
let consumed = egui_ctx.wants_keyboard_input()
|| input.virtual_keycode == Some(winit::event::VirtualKeyCode::Tab);
EventResponse {

View File

@@ -4,7 +4,7 @@ version = "0.22.0"
authors = ["Emil Ernerfeldt <emil.ernerfeldt@gmail.com>"]
description = "An easy-to-use immediate mode GUI that runs on both web and native"
edition = "2021"
rust-version = "1.65"
rust-version = "1.67"
homepage = "https://github.com/emilk/egui"
license = "MIT OR Apache-2.0"
readme = "../../README.md"

View File

@@ -426,7 +426,7 @@ impl Prepared {
temporarily_invisible: _,
} = self;
state.size = content_ui.min_rect().size();
state.size = content_ui.min_size();
ctx.memory_mut(|m| m.areas.set_state(layer_id, state));

View File

@@ -193,9 +193,7 @@ impl Frame {
let where_to_put_background = ui.painter().add(Shape::Noop);
let outer_rect_bounds = ui.available_rect_before_wrap();
let mut inner_rect = outer_rect_bounds;
inner_rect.min += self.outer_margin.left_top() + self.inner_margin.left_top();
inner_rect.max -= self.outer_margin.right_bottom() + self.inner_margin.right_bottom();
let mut inner_rect = (self.inner_margin + self.outer_margin).shrink_rect(outer_rect_bounds);
// Make sure we don't shrink to the negative:
inner_rect.max.x = inner_rect.max.x.max(inner_rect.min.x);
@@ -256,17 +254,13 @@ impl Frame {
impl Prepared {
fn paint_rect(&self) -> Rect {
let mut rect = self.content_ui.min_rect();
rect.min -= self.frame.inner_margin.left_top();
rect.max += self.frame.inner_margin.right_bottom();
rect
self.frame
.inner_margin
.expand_rect(self.content_ui.min_rect())
}
fn content_with_margin(&self) -> Rect {
let mut rect = self.content_ui.min_rect();
rect.min -= self.frame.inner_margin.left_top() + self.frame.outer_margin.left_top();
rect.max += self.frame.inner_margin.right_bottom() + self.frame.outer_margin.right_bottom();
rect
(self.frame.inner_margin + self.frame.outer_margin).expand_rect(self.content_ui.min_rect())
}
pub fn end(self, ui: &mut Ui) -> Response {

View File

@@ -15,8 +15,6 @@
//!
//! Add your [`Window`]:s after any top-level panels.
use std::ops::RangeInclusive;
use crate::*;
/// State regarding panels.
@@ -99,7 +97,7 @@ pub struct SidePanel {
resizable: bool,
show_separator_line: bool,
default_width: f32,
width_range: RangeInclusive<f32>,
width_range: Rangef,
}
impl SidePanel {
@@ -122,7 +120,7 @@ impl SidePanel {
resizable: true,
show_separator_line: true,
default_width: 200.0,
width_range: 96.0..=f32::INFINITY,
width_range: Rangef::new(96.0, f32::INFINITY),
}
}
@@ -153,26 +151,29 @@ impl SidePanel {
/// The initial wrapping width of the [`SidePanel`].
pub fn default_width(mut self, default_width: f32) -> Self {
self.default_width = default_width;
self.width_range = self.width_range.start().at_most(default_width)
..=self.width_range.end().at_least(default_width);
self.width_range = Rangef::new(
self.width_range.min.at_most(default_width),
self.width_range.max.at_least(default_width),
);
self
}
/// Minimum width of the panel.
pub fn min_width(mut self, min_width: f32) -> Self {
self.width_range = min_width..=self.width_range.end().at_least(min_width);
self.width_range = Rangef::new(min_width, self.width_range.max.at_least(min_width));
self
}
/// Maximum width of the panel.
pub fn max_width(mut self, max_width: f32) -> Self {
self.width_range = self.width_range.start().at_most(max_width)..=max_width;
self.width_range = Rangef::new(self.width_range.min.at_most(max_width), max_width);
self
}
/// The allowable width range for the panel.
pub fn width_range(mut self, width_range: RangeInclusive<f32>) -> Self {
self.default_width = clamp_to_range(self.default_width, width_range.clone());
pub fn width_range(mut self, width_range: impl Into<Rangef>) -> Self {
let width_range = width_range.into();
self.default_width = clamp_to_range(self.default_width, width_range);
self.width_range = width_range;
self
}
@@ -180,7 +181,7 @@ impl SidePanel {
/// Enforce this exact width.
pub fn exact_width(mut self, width: f32) -> Self {
self.default_width = width;
self.width_range = width..=width;
self.width_range = Rangef::point(width);
self
}
@@ -224,7 +225,7 @@ impl SidePanel {
if let Some(state) = PanelState::load(ui.ctx(), id) {
width = state.rect.width();
}
width = clamp_to_range(width, width_range.clone()).at_most(available_rect.width());
width = clamp_to_range(width, width_range).at_most(available_rect.width());
side.set_rect_width(&mut panel_rect, width);
ui.ctx().check_for_id_clash(id, panel_rect, "SidePanel");
}
@@ -241,7 +242,7 @@ impl SidePanel {
let resize_x = side.opposite().side_x(panel_rect);
let mouse_over_resize_line = we_are_on_top
&& panel_rect.y_range().contains(&pointer.y)
&& panel_rect.y_range().contains(pointer.y)
&& (resize_x - pointer.x).abs()
<= ui.style().interaction.resize_grab_radius_side;
@@ -253,8 +254,7 @@ impl SidePanel {
is_resizing = ui.memory(|mem| mem.is_being_dragged(resize_id));
if is_resizing {
let width = (pointer.x - side.side_x(panel_rect)).abs();
let width =
clamp_to_range(width, width_range.clone()).at_most(available_rect.width());
let width = clamp_to_range(width, width_range).at_most(available_rect.width());
side.set_rect_width(&mut panel_rect, width);
}
@@ -273,7 +273,7 @@ impl SidePanel {
let frame = frame.unwrap_or_else(|| Frame::side_top_panel(ui.style()));
let inner_response = frame.show(&mut panel_ui, |ui| {
ui.set_min_height(ui.max_rect().height()); // Make sure the frame fills the full height
ui.set_min_width(*width_range.start());
ui.set_min_width(width_range.min);
add_contents(ui)
});
@@ -544,7 +544,7 @@ pub struct TopBottomPanel {
resizable: bool,
show_separator_line: bool,
default_height: Option<f32>,
height_range: RangeInclusive<f32>,
height_range: Rangef,
}
impl TopBottomPanel {
@@ -567,7 +567,7 @@ impl TopBottomPanel {
resizable: false,
show_separator_line: true,
default_height: None,
height_range: 20.0..=f32::INFINITY,
height_range: Rangef::new(20.0, f32::INFINITY),
}
}
@@ -599,28 +599,31 @@ impl TopBottomPanel {
/// Defaults to [`style::Spacing::interact_size`].y.
pub fn default_height(mut self, default_height: f32) -> Self {
self.default_height = Some(default_height);
self.height_range = self.height_range.start().at_most(default_height)
..=self.height_range.end().at_least(default_height);
self.height_range = Rangef::new(
self.height_range.min.at_most(default_height),
self.height_range.max.at_least(default_height),
);
self
}
/// Minimum height of the panel.
pub fn min_height(mut self, min_height: f32) -> Self {
self.height_range = min_height..=self.height_range.end().at_least(min_height);
self.height_range = Rangef::new(min_height, self.height_range.max.at_least(min_height));
self
}
/// Maximum height of the panel.
pub fn max_height(mut self, max_height: f32) -> Self {
self.height_range = self.height_range.start().at_most(max_height)..=max_height;
self.height_range = Rangef::new(self.height_range.min.at_most(max_height), max_height);
self
}
/// The allowable height range for the panel.
pub fn height_range(mut self, height_range: RangeInclusive<f32>) -> Self {
pub fn height_range(mut self, height_range: impl Into<Rangef>) -> Self {
let height_range = height_range.into();
self.default_height = self
.default_height
.map(|default_height| clamp_to_range(default_height, height_range.clone()));
.map(|default_height| clamp_to_range(default_height, height_range));
self.height_range = height_range;
self
}
@@ -628,7 +631,7 @@ impl TopBottomPanel {
/// Enforce this exact height.
pub fn exact_height(mut self, height: f32) -> Self {
self.default_height = Some(height);
self.height_range = height..=height;
self.height_range = Rangef::point(height);
self
}
@@ -673,7 +676,7 @@ impl TopBottomPanel {
} else {
default_height.unwrap_or_else(|| ui.style().spacing.interact_size.y)
};
height = clamp_to_range(height, height_range.clone()).at_most(available_rect.height());
height = clamp_to_range(height, height_range).at_most(available_rect.height());
side.set_rect_height(&mut panel_rect, height);
ui.ctx()
.check_for_id_clash(id, panel_rect, "TopBottomPanel");
@@ -692,7 +695,7 @@ impl TopBottomPanel {
let resize_y = side.opposite().side_y(panel_rect);
let mouse_over_resize_line = we_are_on_top
&& panel_rect.x_range().contains(&pointer.x)
&& panel_rect.x_range().contains(pointer.x)
&& (resize_y - pointer.y).abs()
<= ui.style().interaction.resize_grab_radius_side;
@@ -704,8 +707,8 @@ impl TopBottomPanel {
is_resizing = ui.memory(|mem| mem.interaction.drag_id == Some(resize_id));
if is_resizing {
let height = (pointer.y - side.side_y(panel_rect)).abs();
let height = clamp_to_range(height, height_range.clone())
.at_most(available_rect.height());
let height =
clamp_to_range(height, height_range).at_most(available_rect.height());
side.set_rect_height(&mut panel_rect, height);
}
@@ -724,7 +727,7 @@ impl TopBottomPanel {
let frame = frame.unwrap_or_else(|| Frame::side_top_panel(ui.style()));
let inner_response = frame.show(&mut panel_ui, |ui| {
ui.set_min_width(ui.max_rect().width()); // Make the frame fill full width
ui.set_min_height(*height_range.start());
ui.set_min_height(height_range.min);
add_contents(ui)
});
@@ -1056,9 +1059,7 @@ impl CentralPanel {
}
}
fn clamp_to_range(x: f32, range: RangeInclusive<f32>) -> f32 {
x.clamp(
range.start().min(*range.end()),
range.start().max(*range.end()),
)
fn clamp_to_range(x: f32, range: Rangef) -> f32 {
let range = range.as_positive();
x.clamp(range.min, range.max)
}

View File

@@ -124,7 +124,10 @@ impl Resize {
}
/// Can you resize it with the mouse?
/// Note that a window can still auto-resize
///
/// Note that a window can still auto-resize.
///
/// Default is `true`.
pub fn resizable(mut self, resizable: bool) -> Self {
self.resizable = resizable;
self

View File

@@ -334,16 +334,22 @@ struct Prepared {
state: State,
has_bar: [bool; 2],
auto_shrink: [bool; 2],
/// How much horizontal and vertical space are used up by the
/// width of the vertical bar, and the height of the horizontal bar?
current_bar_use: Vec2,
scroll_bar_visibility: ScrollBarVisibility,
/// Where on the screen the content is (excludes scroll bars).
inner_rect: Rect,
content_ui: Ui,
/// Relative coordinates: the offset and size of the view of the inner UI.
/// `viewport.min == ZERO` means we scrolled to the top.
viewport: Rect,
scrolling_enabled: bool,
stick_to_end: [bool; 2],
}
@@ -459,7 +465,7 @@ impl ScrollArea {
content_clip_rect.max[d] = ui.clip_rect().max[d] - current_bar_use[d];
}
}
// Make sure we din't accidentally expand the clip rect
// Make sure we didn't accidentally expand the clip rect
content_clip_rect = content_clip_rect.intersect(ui.clip_rect());
content_ui.set_clip_rect(content_clip_rect);
}
@@ -640,8 +646,7 @@ impl Prepared {
let min = content_ui.min_rect().min[d];
let clip_rect = content_ui.clip_rect();
let visible_range = min..=min + clip_rect.size()[d];
let start = *scroll.start();
let end = *scroll.end();
let (start, end) = (scroll.min, scroll.max);
let clip_start = clip_rect.min[d];
let clip_end = clip_rect.max[d];
let mut spacing = ui.spacing().item_spacing[d];

View File

@@ -217,7 +217,10 @@ impl<'open> Window<'open> {
}
/// Can the user resize the window by dragging its edges?
///
/// Note that even if you set this to `false` the window may still auto-resize.
///
/// Default is `true`.
pub fn resizable(mut self, resizable: bool) -> Self {
self.resize = self.resize.resizable(resizable);
self

View File

@@ -566,7 +566,7 @@ impl Context {
}
let show_error = |widget_rect: Rect, text: String| {
let text = format!("🔥 {}", text);
let text = format!("🔥 {text}");
let color = self.style().visuals.error_fg_color;
let painter = self.debug_painter();
painter.rect_stroke(widget_rect, 0.0, (1.0, color));
@@ -612,10 +612,10 @@ impl Context {
let id_str = id.short_debug_format();
if prev_rect.min.distance(new_rect.min) < 4.0 {
show_error(new_rect, format!("Double use of {} ID {}", what, id_str));
show_error(new_rect, format!("Double use of {what} ID {id_str}"));
} else {
show_error(prev_rect, format!("First use of {} ID {}", what, id_str));
show_error(new_rect, format!("Second use of {} ID {}", what, id_str));
show_error(prev_rect, format!("First use of {what} ID {id_str}"));
show_error(new_rect, format!("Second use of {what} ID {id_str}"));
}
}
@@ -1574,14 +1574,14 @@ impl Context {
let pointer_pos = self
.pointer_hover_pos()
.map_or_else(String::new, |pos| format!("{:?}", pos));
ui.label(format!("Pointer pos: {}", pointer_pos));
.map_or_else(String::new, |pos| format!("{pos:?}"));
ui.label(format!("Pointer pos: {pointer_pos}"));
let top_layer = self
.pointer_hover_pos()
.and_then(|pos| self.layer_id_at(pos))
.map_or_else(String::new, |layer| layer.short_debug_format());
ui.label(format!("Top layer under mouse: {}", top_layer));
ui.label(format!("Top layer under mouse: {top_layer}"));
ui.add_space(16.0);
@@ -1667,7 +1667,7 @@ impl Context {
ui.image(texture_id, size);
});
ui.label(format!("{} x {}", w, h));
ui.label(format!("{w} x {h}"));
ui.label(format!("{:.3} MB", meta.bytes_used() as f64 * 1e-6));
ui.label(format!("{:?}", meta.name));
ui.end_row();
@@ -1688,8 +1688,7 @@ impl Context {
let (num_state, num_serialized) = self.data(|d| (d.len(), d.count_serialized()));
ui.label(format!(
"{} widget states stored (of which {} are serialized).",
num_state, num_serialized
"{num_state} widget states stored (of which {num_serialized} are serialized)."
));
ui.horizontal(|ui| {

View File

@@ -610,11 +610,11 @@ pub struct ModifierNames<'a> {
}
impl ModifierNames<'static> {
/// ⌥ ^ ⇧ ⌘ - NOTE: not supported by the default egui font.
/// ⌥ ⇧ ⌘ - NOTE: not supported by the default egui font.
pub const SYMBOLS: Self = Self {
is_short: true,
alt: "",
ctrl: "^",
ctrl: "",
shift: "",
mac_cmd: "",
mac_alt: "",
@@ -693,27 +693,37 @@ pub enum Key {
/// The virtual keycode for the Minus key.
Minus,
/// The virtual keycode for the Plus/Equals key.
PlusEquals,
/// Either from the main row or from the numpad.
Num0,
/// Either from the main row or from the numpad.
Num1,
/// Either from the main row or from the numpad.
Num2,
/// Either from the main row or from the numpad.
Num3,
/// Either from the main row or from the numpad.
Num4,
/// Either from the main row or from the numpad.
Num5,
/// Either from the main row or from the numpad.
Num6,
/// Either from the main row or from the numpad.
Num7,
/// Either from the main row or from the numpad.
Num8,
/// Either from the main row or from the numpad.
Num9,
@@ -906,7 +916,7 @@ fn format_kb_shortcut() {
cmd_shift_f.format(&ModifierNames::NAMES, true),
"Shift+Cmd+F"
);
assert_eq!(cmd_shift_f.format(&ModifierNames::SYMBOLS, false), "^⇧F");
assert_eq!(cmd_shift_f.format(&ModifierNames::SYMBOLS, false), "⇧F");
assert_eq!(cmd_shift_f.format(&ModifierNames::SYMBOLS, true), "⇧⌘F");
}
@@ -927,25 +937,25 @@ impl RawInput {
focused,
} = self;
ui.label(format!("screen_rect: {:?} points", screen_rect));
ui.label(format!("pixels_per_point: {:?}", pixels_per_point))
ui.label(format!("screen_rect: {screen_rect:?} points"));
ui.label(format!("pixels_per_point: {pixels_per_point:?}"))
.on_hover_text(
"Also called HDPI factor.\nNumber of physical pixels per each logical pixel.",
);
ui.label(format!("max_texture_side: {:?}", max_texture_side));
ui.label(format!("max_texture_side: {max_texture_side:?}"));
if let Some(time) = time {
ui.label(format!("time: {:.3} s", time));
ui.label(format!("time: {time:.3} s"));
} else {
ui.label("time: None");
}
ui.label(format!("predicted_dt: {:.1} ms", 1e3 * predicted_dt));
ui.label(format!("modifiers: {:#?}", modifiers));
ui.label(format!("modifiers: {modifiers:#?}"));
ui.label(format!("hovered_files: {}", hovered_files.len()));
ui.label(format!("dropped_files: {}", dropped_files.len()));
ui.label(format!("focused: {}", focused));
ui.label(format!("focused: {focused}"));
ui.scope(|ui| {
ui.set_min_height(150.0);
ui.label(format!("events: {:#?}", events))
ui.label(format!("events: {events:#?}"))
.on_hover_text("key presses etc");
});
}

View File

@@ -100,7 +100,7 @@ impl PlatformOutput {
/// This can be used by a text-to-speech system to describe the events (if any).
pub fn events_description(&self) -> String {
// only describe last event:
if let Some(event) = self.events.iter().rev().next() {
if let Some(event) = self.events.iter().next_back() {
match event {
OutputEvent::Clicked(widget_info)
| OutputEvent::DoubleClicked(widget_info)
@@ -417,12 +417,12 @@ impl OutputEvent {
impl std::fmt::Debug for OutputEvent {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Clicked(wi) => write!(f, "Clicked({:?})", wi),
Self::DoubleClicked(wi) => write!(f, "DoubleClicked({:?})", wi),
Self::TripleClicked(wi) => write!(f, "TripleClicked({:?})", wi),
Self::FocusGained(wi) => write!(f, "FocusGained({:?})", wi),
Self::TextSelectionChanged(wi) => write!(f, "TextSelectionChanged({:?})", wi),
Self::ValueChanged(wi) => write!(f, "ValueChanged({:?})", wi),
Self::Clicked(wi) => write!(f, "Clicked({wi:?})"),
Self::DoubleClicked(wi) => write!(f, "DoubleClicked({wi:?})"),
Self::TripleClicked(wi) => write!(f, "TripleClicked({wi:?})"),
Self::FocusGained(wi) => write!(f, "FocusGained({wi:?})"),
Self::TextSelectionChanged(wi) => write!(f, "TextSelectionChanged({wi:?})"),
Self::ValueChanged(wi) => write!(f, "ValueChanged({wi:?})"),
}
}
}
@@ -609,14 +609,14 @@ impl WidgetInfo {
if let Some(selected) = selected {
if *typ == WidgetType::Checkbox {
let state = if *selected { "checked" } else { "unchecked" };
description = format!("{} {}", state, description);
description = format!("{state} {description}");
} else {
description += if *selected { "selected" } else { "" };
};
}
if let Some(label) = label {
description = format!("{}: {}", label, description);
description = format!("{label}: {description}");
}
if typ == &WidgetType::TextEdit {
@@ -630,7 +630,7 @@ impl WidgetInfo {
} else {
text = "blank".into();
}
description = format!("{}: {}", text, description);
description = format!("{text}: {description}");
}
if let Some(value) = value {

View File

@@ -1,5 +1,3 @@
use std::ops::RangeInclusive;
use crate::{id::IdSet, *};
#[derive(Clone, Copy, Debug)]
@@ -46,7 +44,7 @@ pub(crate) struct FrameState {
pub(crate) scroll_delta: Vec2, // TODO(emilk): move to `InputState` ?
/// horizontal, vertical
pub(crate) scroll_target: [Option<(RangeInclusive<f32>, Option<Align>)>; 2],
pub(crate) scroll_target: [Option<(Rangef, Option<Align>)>; 2],
#[cfg(feature = "accesskit")]
pub(crate) accesskit_state: Option<AccessKitFrameState>,

View File

@@ -47,7 +47,7 @@ impl State {
// ----------------------------------------------------------------------------
// type alias for boxed function to determine row color during grid generation
type ColorPickerFn = Box<dyn Fn(usize, &Style) -> Option<Color32>>;
type ColorPickerFn = Box<dyn Send + Sync + Fn(usize, &Style) -> Option<Color32>>;
pub(crate) struct GridLayout {
ctx: Context,
@@ -60,6 +60,7 @@ pub(crate) struct GridLayout {
/// State previous frame (if any).
/// This can be used to predict future sizes of cells.
prev_state: State,
/// State accumulated during the current frame.
curr_state: State,
initial_available: Rect,
@@ -311,7 +312,7 @@ impl Grid {
/// Setting this will allow for dynamic coloring of rows of the grid object
pub fn with_row_color<F>(mut self, color_picker: F) -> Self
where
F: Fn(usize, &Style) -> Option<Color32> + 'static,
F: Send + Sync + Fn(usize, &Style) -> Option<Color32> + 'static,
{
self.color_picker = Some(Box::new(color_picker));
self

View File

@@ -990,30 +990,28 @@ impl InputState {
});
}
ui.label(format!("scroll_delta: {:?} points", scroll_delta));
ui.label(format!("zoom_factor_delta: {:4.2}x", zoom_factor_delta));
ui.label(format!("screen_rect: {:?} points", screen_rect));
ui.label(format!("scroll_delta: {scroll_delta:?} points"));
ui.label(format!("zoom_factor_delta: {zoom_factor_delta:4.2}x"));
ui.label(format!("screen_rect: {screen_rect:?} points"));
ui.label(format!(
"{} physical pixels for each logical point",
pixels_per_point
"{pixels_per_point} physical pixels for each logical point"
));
ui.label(format!(
"max texture size (on each side): {}",
max_texture_side
"max texture size (on each side): {max_texture_side}"
));
ui.label(format!("time: {:.3} s", time));
ui.label(format!("time: {time:.3} s"));
ui.label(format!(
"time since previous frame: {:.1} ms",
1e3 * unstable_dt
));
ui.label(format!("predicted_dt: {:.1} ms", 1e3 * predicted_dt));
ui.label(format!("stable_dt: {:.1} ms", 1e3 * stable_dt));
ui.label(format!("focused: {}", focused));
ui.label(format!("modifiers: {:#?}", modifiers));
ui.label(format!("keys_down: {:?}", keys_down));
ui.label(format!("focused: {focused}"));
ui.label(format!("modifiers: {modifiers:#?}"));
ui.label(format!("keys_down: {keys_down:?}"));
ui.scope(|ui| {
ui.set_min_height(150.0);
ui.label(format!("events: {:#?}", events))
ui.label(format!("events: {events:#?}"))
.on_hover_text("key presses etc");
});
}
@@ -1037,22 +1035,21 @@ impl PointerState {
pointer_events,
} = self;
ui.label(format!("latest_pos: {:?}", latest_pos));
ui.label(format!("interact_pos: {:?}", interact_pos));
ui.label(format!("delta: {:?}", delta));
ui.label(format!("latest_pos: {latest_pos:?}"));
ui.label(format!("interact_pos: {interact_pos:?}"));
ui.label(format!("delta: {delta:?}"));
ui.label(format!(
"velocity: [{:3.0} {:3.0}] points/sec",
velocity.x, velocity.y
));
ui.label(format!("down: {:#?}", down));
ui.label(format!("press_origin: {:?}", press_origin));
ui.label(format!("press_start_time: {:?} s", press_start_time));
ui.label(format!("down: {down:#?}"));
ui.label(format!("press_origin: {press_origin:?}"));
ui.label(format!("press_start_time: {press_start_time:?} s"));
ui.label(format!(
"has_moved_too_much_for_a_click: {}",
has_moved_too_much_for_a_click
"has_moved_too_much_for_a_click: {has_moved_too_much_for_a_click}"
));
ui.label(format!("last_click_time: {:#?}", last_click_time));
ui.label(format!("last_last_click_time: {:#?}", last_last_click_time));
ui.label(format!("pointer_events: {:?}", pointer_events));
ui.label(format!("last_click_time: {last_click_time:#?}"));
ui.label(format!("last_last_click_time: {last_last_click_time:#?}"));
ui.label(format!("pointer_events: {pointer_events:?}"));
}
}

View File

@@ -286,7 +286,7 @@ impl TouchState {
impl TouchState {
pub fn ui(&self, ui: &mut crate::Ui) {
ui.label(format!("{:?}", self));
ui.label(format!("{self:?}"));
}
}
@@ -294,7 +294,7 @@ impl Debug for TouchState {
// This outputs less clutter than `#[derive(Debug)]`:
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
for (id, touch) in &self.active_touches {
f.write_fmt(format_args!("#{:?}: {:#?}\n", id, touch))?;
f.write_fmt(format_args!("#{id:?}: {touch:#?}\n"))?;
}
f.write_fmt(format_args!("gesture: {:#?}\n", self.gesture_state))?;
Ok(())

View File

@@ -31,10 +31,7 @@ pub(crate) fn font_texture_ui(ui: &mut Ui, [width, height]: [usize; 2]) -> Respo
Color32::BLACK
};
ui.label(format!(
"Texture size: {} x {} (hover to zoom)",
width, height
));
ui.label(format!("Texture size: {width} x {height} (hover to zoom)"));
if width <= 1 || height <= 1 {
return;
}
@@ -108,7 +105,7 @@ impl Widget for &epaint::stats::PaintStats {
label(ui, shape_path, "paths");
label(ui, shape_mesh, "nested meshes");
label(ui, shape_vec, "nested shapes");
ui.label(format!("{:6} callbacks", num_callbacks));
ui.label(format!("{num_callbacks:6} callbacks"));
ui.add_space(10.0);
ui.label("Text shapes:");

View File

@@ -127,7 +127,7 @@ impl PaintList {
#[inline(always)]
pub fn add(&mut self, clip_rect: Rect, shape: Shape) -> ShapeIdx {
let idx = ShapeIdx(self.0.len());
self.0.push(ClippedShape(clip_rect, shape));
self.0.push(ClippedShape { clip_rect, shape });
idx
}
@@ -135,7 +135,7 @@ impl PaintList {
self.0.extend(
shapes
.into_iter()
.map(|shape| ClippedShape(clip_rect, shape)),
.map(|shape| ClippedShape { clip_rect, shape }),
);
}
@@ -148,12 +148,12 @@ impl PaintList {
/// and then later setting it using `paint_list.set(idx, cr, frame);`.
#[inline(always)]
pub fn set(&mut self, idx: ShapeIdx, clip_rect: Rect, shape: Shape) {
self.0[idx.0] = ClippedShape(clip_rect, shape);
self.0[idx.0] = ClippedShape { clip_rect, shape };
}
/// Translate each [`Shape`] and clip rectangle by this much, in-place
pub fn translate(&mut self, delta: Vec2) {
for ClippedShape(clip_rect, shape) in &mut self.0 {
for ClippedShape { clip_rect, shape } in &mut self.0 {
*clip_rect = clip_rect.translate(delta);
shape.translate(delta);
}

View File

@@ -335,7 +335,9 @@ pub use epaint::emath;
#[cfg(feature = "color-hex")]
pub use ecolor::hex_color;
pub use ecolor::{Color32, Rgba};
pub use emath::{lerp, pos2, remap, remap_clamp, vec2, Align, Align2, NumExt, Pos2, Rect, Vec2};
pub use emath::{
lerp, pos2, remap, remap_clamp, vec2, Align, Align2, NumExt, Pos2, Rangef, Rect, Vec2,
};
pub use epaint::{
mutex,
text::{FontData, FontDefinitions, FontFamily, FontId, FontTweak},

View File

@@ -546,8 +546,10 @@ impl Memory {
#[cfg_attr(feature = "serde", serde(default))]
pub struct Areas {
areas: IdMap<area::State>,
/// Back-to-front. Top is last.
order: Vec<LayerId>,
visible_last_frame: ahash::HashSet<LayerId>,
visible_current_frame: ahash::HashSet<LayerId>,

View File

@@ -1,8 +1,7 @@
use std::ops::RangeInclusive;
use std::sync::Arc;
use crate::{
emath::{Align2, Pos2, Rect, Vec2},
emath::{Align2, Pos2, Rangef, Rect, Vec2},
layers::{LayerId, PaintList, ShapeIdx},
Color32, Context, FontId,
};
@@ -227,7 +226,7 @@ impl Painter {
pub fn error(&self, pos: Pos2, text: impl std::fmt::Display) -> Rect {
let color = self.ctx.style().visuals.error_fg_color;
self.debug_text(pos, Align2::LEFT_TOP, color, format!("🔥 {}", text))
self.debug_text(pos, Align2::LEFT_TOP, color, format!("🔥 {text}"))
}
/// text with a background
@@ -263,12 +262,12 @@ impl Painter {
}
/// Paints a horizontal line.
pub fn hline(&self, x: RangeInclusive<f32>, y: f32, stroke: impl Into<Stroke>) {
pub fn hline(&self, x: impl Into<Rangef>, y: f32, stroke: impl Into<Stroke>) {
self.add(Shape::hline(x, y, stroke));
}
/// Paints a vertical line.
pub fn vline(&self, x: f32, y: RangeInclusive<f32>, stroke: impl Into<Stroke>) {
pub fn vline(&self, x: f32, y: impl Into<Rangef>, stroke: impl Into<Stroke>) {
self.add(Shape::vline(x, y, stroke));
}

View File

@@ -360,30 +360,46 @@ impl Margin {
}
/// Total margins on both sides
#[inline]
pub fn sum(&self) -> Vec2 {
vec2(self.left + self.right, self.top + self.bottom)
}
#[inline]
pub fn left_top(&self) -> Vec2 {
vec2(self.left, self.top)
}
#[inline]
pub fn right_bottom(&self) -> Vec2 {
vec2(self.right, self.bottom)
}
#[inline]
pub fn is_same(&self) -> bool {
self.left == self.right && self.left == self.top && self.left == self.bottom
}
#[inline]
pub fn expand_rect(&self, rect: Rect) -> Rect {
Rect::from_min_max(rect.min - self.left_top(), rect.max + self.right_bottom())
}
#[inline]
pub fn shrink_rect(&self, rect: Rect) -> Rect {
Rect::from_min_max(rect.min + self.left_top(), rect.max - self.right_bottom())
}
}
impl From<f32> for Margin {
#[inline]
fn from(v: f32) -> Self {
Self::same(v)
}
}
impl From<Vec2> for Margin {
#[inline]
fn from(v: Vec2) -> Self {
Self::symmetric(v.x, v.y)
}
@@ -392,6 +408,7 @@ impl From<Vec2> for Margin {
impl std::ops::Add for Margin {
type Output = Self;
#[inline]
fn add(self, other: Self) -> Self {
Self {
left: self.left + other.left,
@@ -491,7 +508,8 @@ pub struct Visuals {
pub resize_corner_size: f32,
pub text_cursor_width: f32,
/// The color and width of the text cursor
pub text_cursor: Stroke,
/// show where the text cursor would be if you clicked
pub text_cursor_preview: bool,
@@ -767,7 +785,7 @@ impl Visuals {
popup_shadow: Shadow::small_dark(),
resize_corner_size: 12.0,
text_cursor_width: 2.0,
text_cursor: Stroke::new(2.0, Color32::from_rgb(192, 222, 255)),
text_cursor_preview: false,
clip_rect_margin: 3.0, // should be at least half the size of the widest frame stroke + max WidgetVisuals::expansion
button_frame: true,
@@ -800,6 +818,7 @@ impl Visuals {
panel_fill: Color32::from_gray(248),
popup_shadow: Shadow::small_light(),
text_cursor: Stroke::new(2.0, Color32::from_rgb(0, 83, 125)),
..Self::dark()
}
}
@@ -1334,7 +1353,7 @@ impl Visuals {
popup_shadow,
resize_corner_size,
text_cursor_width,
text_cursor,
text_cursor_preview,
clip_rect_margin,
button_frame,
@@ -1392,8 +1411,9 @@ impl Visuals {
});
ui_color(ui, hyperlink_color, "hyperlink_color");
stroke_ui(ui, text_cursor, "Text Cursor");
ui.add(Slider::new(resize_corner_size, 0.0..=20.0).text("resize_corner_size"));
ui.add(Slider::new(text_cursor_width, 0.0..=4.0).text("text_cursor_width"));
ui.checkbox(text_cursor_preview, "Preview text cursor on hover");
ui.add(Slider::new(clip_rect_margin, 0.0..=20.0).text("clip_rect_margin"));

View File

@@ -517,15 +517,17 @@ impl Ui {
}
/// `ui.set_width_range(min..=max);` is equivalent to `ui.set_min_width(min); ui.set_max_width(max);`.
pub fn set_width_range(&mut self, width: std::ops::RangeInclusive<f32>) {
self.set_min_width(*width.start());
self.set_max_width(*width.end());
pub fn set_width_range(&mut self, width: impl Into<Rangef>) {
let width = width.into();
self.set_min_width(width.min);
self.set_max_width(width.max);
}
/// `ui.set_height_range(min..=max);` is equivalent to `ui.set_min_height(min); ui.set_max_height(max);`.
pub fn set_height_range(&mut self, height: std::ops::RangeInclusive<f32>) {
self.set_min_height(*height.start());
self.set_max_height(*height.end());
pub fn set_height_range(&mut self, height: impl Into<Rangef>) {
let height = height.into();
self.set_min_height(height.min);
self.set_max_height(height.max);
}
/// Set both the minimum and maximum width.
@@ -556,6 +558,7 @@ impl Ui {
// Layout related measures:
/// The available space at the moment, given the current cursor.
///
/// This how much more space we can take up without overflowing our parent.
/// Shrinks as widgets allocate space and the cursor moves.
/// A small size should be interpreted as "as little as possible".
@@ -564,19 +567,30 @@ impl Ui {
self.placer.available_size()
}
/// The available width at the moment, given the current cursor.
///
/// See [`Self::available_size`] for more information.
pub fn available_width(&self) -> f32 {
self.available_size().x
}
/// The available height at the moment, given the current cursor.
///
/// See [`Self::available_size`] for more information.
pub fn available_height(&self) -> f32 {
self.available_size().y
}
/// In case of a wrapping layout, how much space is left on this row/column?
///
/// If the layout does not wrap, this will return the same value as [`Self::available_size`].
pub fn available_size_before_wrap(&self) -> Vec2 {
self.placer.available_rect_before_wrap().size()
}
/// In case of a wrapping layout, how much space is left on this row/column?
///
/// If the layout does not wrap, this will return the same value as [`Self::available_size`].
pub fn available_rect_before_wrap(&self) -> Rect {
self.placer.available_rect_before_wrap()
}
@@ -966,7 +980,7 @@ impl Ui {
/// ```
pub fn scroll_to_rect(&self, rect: Rect, align: Option<Align>) {
for d in 0..2 {
let range = rect.min[d]..=rect.max[d];
let range = Rangef::new(rect.min[d], rect.max[d]);
self.ctx()
.frame_state_mut(|state| state.scroll_target[d] = Some((range, align)));
}
@@ -996,9 +1010,9 @@ impl Ui {
pub fn scroll_to_cursor(&self, align: Option<Align>) {
let target = self.next_widget_position();
for d in 0..2 {
let target = target[d];
let target = Rangef::point(target[d]);
self.ctx()
.frame_state_mut(|state| state.scroll_target[d] = Some((target..=target, align)));
.frame_state_mut(|state| state.scroll_target[d] = Some((target, align)));
}
}
@@ -2231,3 +2245,9 @@ impl Ui {
}
}
}
#[test]
fn ui_impl_send_sync() {
fn assert_send_sync<T: Send + Sync>() {}
assert_send_sync::<Ui>();
}

View File

@@ -23,6 +23,7 @@ pub struct Button {
text: WidgetText,
shortcut_text: WidgetText,
wrap: Option<bool>,
/// None means default for interact
fill: Option<Color32>,
stroke: Option<Stroke>,

View File

@@ -234,17 +234,17 @@ fn color_text_ui(ui: &mut Ui, color: impl Into<Color32>, alpha: Alpha) {
if ui.button("📋").on_hover_text("Click to copy").clicked() {
if alpha == Alpha::Opaque {
ui.output_mut(|o| o.copied_text = format!("{}, {}, {}", r, g, b));
ui.output_mut(|o| o.copied_text = format!("{r}, {g}, {b}"));
} else {
ui.output_mut(|o| o.copied_text = format!("{}, {}, {}, {}", r, g, b, a));
ui.output_mut(|o| o.copied_text = format!("{r}, {g}, {b}, {a}"));
}
}
if alpha == Alpha::Opaque {
ui.label(format!("rgb({}, {}, {})", r, g, b))
ui.label(format!("rgb({r}, {g}, {b})"))
.on_hover_text("Red Green Blue");
} else {
ui.label(format!("rgba({}, {}, {}, {})", r, g, b, a))
ui.label(format!("rgba({r}, {g}, {b}, {a})"))
.on_hover_text("Red Green Blue with premultiplied Alpha");
}
});

View File

@@ -11,6 +11,7 @@ use crate::*;
pub(crate) struct MonoState {
last_dragged_id: Option<Id>,
last_dragged_value: Option<f64>,
/// For temporary edit of a [`DragValue`] value.
/// Couples with the current focus id.
edit_string: Option<String>,
@@ -63,6 +64,7 @@ pub struct DragValue<'a> {
max_decimals: Option<usize>,
custom_formatter: Option<NumFormatter<'a>>,
custom_parser: Option<NumParser<'a>>,
update_while_editing: bool,
}
impl<'a> DragValue<'a> {
@@ -94,6 +96,7 @@ impl<'a> DragValue<'a> {
max_decimals: None,
custom_formatter: None,
custom_parser: None,
update_while_editing: true,
}
}
@@ -352,6 +355,15 @@ impl<'a> DragValue<'a> {
}
.custom_parser(|s| i64::from_str_radix(s, 16).map(|n| n as f64).ok())
}
/// Update the value on each key press when text-editing the value.
///
/// Default: `true`.
/// If `false`, the value will only be updated when user presses enter or deselects the value.
pub fn update_while_editing(mut self, update: bool) -> Self {
self.update_while_editing = update;
self
}
}
impl<'a> Widget for DragValue<'a> {
@@ -366,6 +378,7 @@ impl<'a> Widget for DragValue<'a> {
max_decimals,
custom_formatter,
custom_parser,
update_while_editing,
} = self;
let shift = ui.input(|i| i.modifiers.shift_only());
@@ -392,7 +405,9 @@ impl<'a> Widget for DragValue<'a> {
let auto_decimals = (aim_rad / speed.abs()).log10().ceil().clamp(0.0, 15.0) as usize;
let auto_decimals = auto_decimals + is_slow_speed as usize;
let max_decimals = max_decimals.unwrap_or(auto_decimals + 2);
let max_decimals = max_decimals
.unwrap_or(auto_decimals + 2)
.at_least(min_decimals);
let auto_decimals = auto_decimals.clamp(min_decimals, max_decimals);
let change = ui.input_mut(|input| {
@@ -475,9 +490,15 @@ impl<'a> Widget for DragValue<'a> {
.desired_width(ui.spacing().interact_size.x)
.font(text_style),
);
// Only update the value when the user presses enter, or clicks elsewhere. NOT every frame.
// See https://github.com/emilk/egui/issues/2687
if response.lost_focus() {
let update = if update_while_editing {
// Update when the edit content has changed.
response.changed()
} else {
// Update only when the edit has lost focus.
response.lost_focus()
};
if update {
let parsed_value = match custom_parser {
Some(parser) => parser(&value_text),
None => value_text.parse().ok(),
@@ -606,7 +627,7 @@ impl<'a> Widget for DragValue<'a> {
// The value is exposed as a string by the text edit widget
// when in edit mode.
if !is_kb_editing {
let value_text = format!("{}{}{}", prefix, value_text, suffix);
let value_text = format!("{prefix}{value_text}{suffix}");
builder.set_value(value_text);
}
});

View File

@@ -760,15 +760,22 @@ impl PlotItem for Text {
/// A set of points.
pub struct Points {
pub(super) series: PlotPoints,
pub(super) shape: MarkerShape,
/// Color of the marker. `Color32::TRANSPARENT` means that it will be picked automatically.
pub(super) color: Color32,
/// Whether to fill the marker. Does not apply to all types.
pub(super) filled: bool,
/// The maximum extent of the marker from its center.
pub(super) radius: f32,
pub(super) name: String,
pub(super) highlight: bool,
pub(super) stems: Option<f32>,
}
@@ -997,6 +1004,7 @@ impl PlotItem for Points {
pub struct Arrows {
pub(super) origins: PlotPoints,
pub(super) tips: PlotPoints,
pub(super) tip_length: Option<f32>,
pub(super) color: Color32,
pub(super) name: String,
pub(super) highlight: bool,
@@ -1007,6 +1015,7 @@ impl Arrows {
Self {
origins: origins.into(),
tips: tips.into(),
tip_length: None,
color: Color32::TRANSPARENT,
name: Default::default(),
highlight: false,
@@ -1019,6 +1028,12 @@ impl Arrows {
self
}
/// Set the length of the arrow tips
pub fn tip_length(mut self, tip_length: f32) -> Self {
self.tip_length = Some(tip_length);
self
}
/// Set the arrows' color.
pub fn color(mut self, color: impl Into<Color32>) -> Self {
self.color = color.into();
@@ -1044,6 +1059,7 @@ impl PlotItem for Arrows {
let Self {
origins,
tips,
tip_length,
color,
highlight,
..
@@ -1062,7 +1078,11 @@ impl PlotItem for Arrows {
.for_each(|(origin, tip)| {
let vector = tip - origin;
let rot = Rot2::from_angle(std::f32::consts::TAU / 10.0);
let tip_length = vector.length() / 4.0;
let tip_length = if let Some(tip_length) = tip_length {
*tip_length
} else {
vector.length() / 4.0
};
let tip = origin + vector;
let dir = vector.normalized();
shapes.push(Shape::line_segment([origin, tip], stroke));
@@ -1119,6 +1139,7 @@ pub struct PlotImage {
pub(super) tint: Color32,
pub(super) highlight: bool,
pub(super) name: String,
pub(crate) rotation: Option<(f32, Vec2)>,
}
impl PlotImage {
@@ -1137,6 +1158,7 @@ impl PlotImage {
size: size.into(),
bg_fill: Default::default(),
tint: Color32::WHITE,
rotation: None,
}
}
@@ -1175,6 +1197,17 @@ impl PlotImage {
self.name = name.to_string();
self
}
/// Rotate the image about an origin by some angle
///
/// Positive angle is clockwise.
/// Origin is a vector in normalized UV space ((0,0) in top-left, (1,1) bottom right).
///
/// To rotate about the center you can pass `Vec2::splat(0.5)` as the origin.
pub fn rotate(mut self, angle: f32, origin: Vec2) -> Self {
self.rotation = Some((angle, origin));
self
}
}
impl PlotItem for PlotImage {
@@ -1202,11 +1235,14 @@ impl PlotItem for PlotImage {
let right_bottom_tf = transform.position_from_point(&right_bottom);
Rect::from_two_pos(left_top_tf, right_bottom_tf)
};
Image::new(*texture_id, *size)
let mut image = Image::new(*texture_id, *size)
.bg_fill(*bg_fill)
.tint(*tint)
.uv(*uv)
.paint_at(ui, rect);
.uv(*uv);
if let Some((angle, origin)) = self.rotation {
image = image.rotate(angle, origin);
}
image.paint_at(ui, rect);
if *highlight {
shapes.push(Shape::rect_stroke(
rect,
@@ -1261,8 +1297,10 @@ pub struct BarChart {
pub(super) bars: Vec<Bar>,
pub(super) default_color: Color32,
pub(super) name: String,
/// A custom element formatter
pub(super) element_formatter: Option<Box<dyn Fn(&Bar, &BarChart) -> String>>,
highlight: bool,
}
@@ -1431,8 +1469,10 @@ pub struct BoxPlot {
pub(super) boxes: Vec<BoxElem>,
pub(super) default_color: Color32,
pub(super) name: String,
/// A custom element formatter
pub(super) element_formatter: Option<Box<dyn Fn(&BoxElem, &BoxPlot) -> String>>,
highlight: bool,
}
@@ -1692,7 +1732,7 @@ pub(super) fn rulers_at_value(
let mut prefix = String::new();
if !name.is_empty() {
prefix = format!("{}\n", name);
prefix = format!("{name}\n");
}
let text = {

View File

@@ -125,8 +125,8 @@ impl ToString for LineStyle {
fn to_string(&self) -> String {
match self {
LineStyle::Solid => "Solid".into(),
LineStyle::Dotted { spacing } => format!("Dotted{}Px", spacing),
LineStyle::Dashed { length } => format!("Dashed{}Px", length),
LineStyle::Dotted { spacing } => format!("Dotted{spacing}Px"),
LineStyle::Dashed { length } => format!("Dashed{length}Px"),
}
}
}

View File

@@ -101,9 +101,11 @@ struct PlotMemory {
/// Indicates if the user has modified the bounds, for example by moving or zooming,
/// or if the bounds should be calculated based by included point or auto bounds.
bounds_modified: AxisBools,
hovered_entry: Option<String>,
hidden_items: ahash::HashSet<String>,
last_plot_transform: PlotTransform,
/// Allows to remember the first click position when performing a boxed zoom
last_click_pos_for_zoom: Option<Pos2>,
}
@@ -864,7 +866,7 @@ impl Plot {
delta.y = 0.0;
}
transform.translate_bounds(delta);
bounds_modified = true.into();
bounds_modified = allow_drag;
}
// Zooming
@@ -935,7 +937,7 @@ impl Plot {
}
if zoom_factor != Vec2::splat(1.0) {
transform.zoom(zoom_factor, hover_pos);
bounds_modified = true.into();
bounds_modified = allow_zoom;
}
}
if allow_scroll {
@@ -1081,17 +1083,25 @@ impl PlotUi {
.push(BoundsModification::Translate(delta_pos));
}
/// Can be used to check if the plot was hovered or clicked.
pub fn response(&self) -> &Response {
&self.response
}
/// Returns `true` if the plot area is currently hovered.
#[deprecated = "Use plot_ui.response().hovered()"]
pub fn plot_hovered(&self) -> bool {
self.response.hovered()
}
/// Returns `true` if the plot was clicked by the primary button.
#[deprecated = "Use plot_ui.response().clicked()"]
pub fn plot_clicked(&self) -> bool {
self.response.clicked()
}
/// Returns `true` if the plot was clicked by the secondary button.
#[deprecated = "Use plot_ui.response().secondary_clicked()"]
pub fn plot_secondary_clicked(&self) -> bool {
self.response.secondary_clicked()
}
@@ -1443,7 +1453,7 @@ impl PreparedPlot {
let axis_range = match axis {
0 => bounds.range_x(),
1 => bounds.range_y(),
_ => panic!("Axis {} does not exist.", axis),
_ => panic!("Axis {axis} does not exist."),
};
let font_id = TextStyle::Body.resolve(ui.style());
@@ -1666,7 +1676,7 @@ pub fn format_number(number: f64, num_decimals: usize) -> String {
let is_integral = number as i64 as f64 == number;
if is_integral {
// perfect integer - show it as such:
format!("{:.0}", number)
format!("{number:.0}")
} else {
// make sure we tell the user it is not an integer by always showing a decimal or two:
format!("{:.*}", num_decimals.at_least(1), number)

View File

@@ -77,8 +77,10 @@ pub struct Slider<'a> {
prefix: String,
suffix: String,
text: WidgetText,
/// Sets the minimal step of the widget value
step: Option<f64>,
drag_value_speed: Option<f64>,
min_decimals: usize,
max_decimals: Option<usize>,
@@ -524,12 +526,12 @@ impl<'a> Slider<'a> {
}
/// For instance, `position` is the mouse position and `position_range` is the physical location of the slider on the screen.
fn value_from_position(&self, position: f32, position_range: RangeInclusive<f32>) -> f64 {
fn value_from_position(&self, position: f32, position_range: Rangef) -> f64 {
let normalized = remap_clamp(position, position_range, 0.0..=1.0) as f64;
value_from_normalized(normalized, self.range(), &self.spec)
}
fn position_from_value(&self, value: f64, position_range: RangeInclusive<f32>) -> f32 {
fn position_from_value(&self, value: f64, position_range: Rangef) -> f32 {
let normalized = normalized_from_value(value, self.range(), &self.spec);
lerp(position_range, normalized as f32)
}
@@ -555,11 +557,11 @@ impl<'a> Slider<'a> {
let new_value = if self.smart_aim {
let aim_radius = ui.input(|i| i.aim_radius());
emath::smart_aim::best_in_range_f64(
self.value_from_position(position - aim_radius, position_range.clone()),
self.value_from_position(position + aim_radius, position_range.clone()),
self.value_from_position(position - aim_radius, position_range),
self.value_from_position(position + aim_radius, position_range),
)
} else {
self.value_from_position(position, position_range.clone())
self.value_from_position(position, position_range)
};
self.set_value(new_value);
}
@@ -594,18 +596,18 @@ impl<'a> Slider<'a> {
if kb_step != 0.0 {
let prev_value = self.get_value();
let prev_position = self.position_from_value(prev_value, position_range.clone());
let prev_position = self.position_from_value(prev_value, position_range);
let new_position = prev_position + kb_step;
let new_value = match self.step {
Some(step) => prev_value + (kb_step as f64 * step),
None if self.smart_aim => {
let aim_radius = ui.input(|i| i.aim_radius());
emath::smart_aim::best_in_range_f64(
self.value_from_position(new_position - aim_radius, position_range.clone()),
self.value_from_position(new_position + aim_radius, position_range.clone()),
self.value_from_position(new_position - aim_radius, position_range),
self.value_from_position(new_position + aim_radius, position_range),
)
}
_ => self.value_from_position(new_position, position_range.clone()),
_ => self.value_from_position(new_position, position_range),
};
self.set_value(new_value);
}
@@ -686,15 +688,11 @@ impl<'a> Slider<'a> {
}
}
fn position_range(&self, rect: &Rect) -> RangeInclusive<f32> {
fn position_range(&self, rect: &Rect) -> Rangef {
let handle_radius = self.handle_radius(rect);
match self.orientation {
SliderOrientation::Horizontal => {
(rect.left() + handle_radius)..=(rect.right() - handle_radius)
}
SliderOrientation::Vertical => {
(rect.bottom() - handle_radius)..=(rect.top() + handle_radius)
}
SliderOrientation::Horizontal => rect.x_range().shrink(handle_radius),
SliderOrientation::Vertical => rect.y_range().shrink(handle_radius),
}
}
@@ -726,7 +724,7 @@ impl<'a> Slider<'a> {
}
}
fn value_ui(&mut self, ui: &mut Ui, position_range: RangeInclusive<f32>) -> Response {
fn value_ui(&mut self, ui: &mut Ui, position_range: Rangef) -> Response {
// If [`DragValue`] is controlled from the keyboard and `step` is defined, set speed to `step`
let change = ui.input(|input| {
input.num_presses(Key::ArrowUp) as i32 + input.num_presses(Key::ArrowRight) as i32
@@ -740,7 +738,7 @@ impl<'a> Slider<'a> {
step
} else {
self.drag_value_speed
.unwrap_or_else(|| self.current_gradient(&position_range))
.unwrap_or_else(|| self.current_gradient(position_range))
};
let mut value = self.get_value();
@@ -767,12 +765,11 @@ impl<'a> Slider<'a> {
}
/// delta(value) / delta(points)
fn current_gradient(&mut self, position_range: &RangeInclusive<f32>) -> f64 {
fn current_gradient(&mut self, position_range: Rangef) -> f64 {
// TODO(emilk): handle clamping
let value = self.get_value();
let value_from_pos =
|position: f32| self.value_from_position(position, position_range.clone());
let pos_from_value = |value: f64| self.position_from_value(value, position_range.clone());
let value_from_pos = |position: f32| self.value_from_position(position, position_range);
let pos_from_value = |value: f64| self.position_from_value(value, position_range);
let left_value = value_from_pos(pos_from_value(value) - 0.5);
let right_value = value_from_pos(pos_from_value(value) + 0.5);
right_value - left_value

View File

@@ -1138,7 +1138,7 @@ fn paint_cursor_end(
galley: &Galley,
cursor: &Cursor,
) -> Rect {
let stroke = ui.visuals().selection.stroke;
let stroke = ui.visuals().text_cursor;
let mut cursor_pos = galley.pos_from_cursor(cursor).translate(pos.to_vec2());
cursor_pos.max.y = cursor_pos.max.y.at_least(cursor_pos.min.y + row_height); // Handle completely empty galleys
@@ -1147,10 +1147,7 @@ fn paint_cursor_end(
let top = cursor_pos.center_top();
let bottom = cursor_pos.center_bottom();
painter.line_segment(
[top, bottom],
(ui.visuals().text_cursor_width, stroke.color),
);
painter.line_segment([top, bottom], (stroke.width, stroke.color));
if false {
// Roof/floor:
@@ -1185,7 +1182,7 @@ fn insert_text(
if char_limit < usize::MAX {
let mut new_string = text_to_insert;
// Avoid subtract with overflow panic
let cutoff = char_limit.saturating_sub(text.as_str().len());
let cutoff = char_limit.saturating_sub(text.as_str().chars().count());
new_string = match new_string.char_indices().nth(cutoff) {
None => new_string,

View File

@@ -4,7 +4,7 @@ version = "0.22.0"
authors = ["Emil Ernerfeldt <emil.ernerfeldt@gmail.com>"]
license = "MIT OR Apache-2.0"
edition = "2021"
rust-version = "1.65"
rust-version = "1.67"
publish = false
default-run = "egui_demo_app"

View File

@@ -133,7 +133,7 @@ fn ui_url(ui: &mut egui::Ui, frame: &mut eframe::Frame, url: &mut String) -> boo
if ui.button("Random image").clicked() {
let seed = ui.input(|i| i.time);
let side = 640;
*url = format!("https://picsum.photos/seed/{}/{}", seed, side);
*url = format!("https://picsum.photos/seed/{seed}/{side}");
trigger_fetch = true;
}
});

View File

@@ -232,8 +232,7 @@ impl BackendPanel {
if ui
.add_enabled(enabled, egui::Button::new("Reset"))
.on_hover_text(format!(
"Reset scale to native value ({:.1})",
native_pixels_per_point
"Reset scale to native value ({native_pixels_per_point:.1})"
))
.clicked()
{
@@ -441,7 +440,7 @@ impl EguiWindows {
.stick_to_bottom(true)
.show(ui, |ui| {
for event in output_event_history {
ui.label(format!("{:?}", event));
ui.label(format!("{event:?}"));
}
});
});

View File

@@ -354,7 +354,7 @@ impl WrapApp {
{
selected_anchor = anchor;
if frame.is_web() {
ui.output_mut(|o| o.open_url(format!("#{}", anchor)));
ui.output_mut(|o| o.open_url(format!("#{anchor}")));
}
}
}

View File

@@ -4,7 +4,7 @@ version = "0.22.0"
authors = ["Emil Ernerfeldt <emil.ernerfeldt@gmail.com>"]
description = "Example library for egui"
edition = "2021"
rust-version = "1.65"
rust-version = "1.67"
homepage = "https://github.com/emilk/egui/tree/master/crates/egui_demo_lib"
license = "MIT OR Apache-2.0"
readme = "README.md"

View File

@@ -83,11 +83,11 @@ fn about_immediate_mode(ui: &mut egui::Ui) {
fn links(ui: &mut egui::Ui) {
use egui::special_emojis::{GITHUB, TWITTER};
ui.hyperlink_to(
format!("{} egui on GitHub", GITHUB),
format!("{GITHUB} egui on GitHub"),
"https://github.com/emilk/egui",
);
ui.hyperlink_to(
format!("{} @ernerfeldt", TWITTER),
format!("{TWITTER} @ernerfeldt"),
"https://twitter.com/ernerfeldt",
);
ui.hyperlink_to("egui documentation", "https://docs.rs/egui/");

View File

@@ -121,7 +121,7 @@ impl CodeExample {
ui.separator();
code_view_ui(ui, &format!("{:#?}", self));
code_view_ui(ui, &format!("{self:#?}"));
ui.separator();

View File

@@ -251,11 +251,11 @@ impl DemoWindows {
use egui::special_emojis::{GITHUB, TWITTER};
ui.hyperlink_to(
format!("{} egui on GitHub", GITHUB),
format!("{GITHUB} egui on GitHub"),
"https://github.com/emilk/egui",
);
ui.hyperlink_to(
format!("{} @ernerfeldt", TWITTER),
format!("{TWITTER} @ernerfeldt"),
"https://twitter.com/ernerfeldt",
);

View File

@@ -140,7 +140,7 @@ impl LayoutTest {
Direction::TopDown,
Direction::BottomUp,
] {
ui.radio_value(&mut self.layout.main_dir, dir, format!("{:?}", dir));
ui.radio_value(&mut self.layout.main_dir, dir, format!("{dir:?}"));
}
});
@@ -162,7 +162,7 @@ impl LayoutTest {
ui.horizontal(|ui| {
ui.label("Cross Align:");
for &align in &[Align::Min, Align::Center, Align::Max] {
ui.radio_value(&mut self.layout.cross_align, align, format!("{:?}", align));
ui.radio_value(&mut self.layout.cross_align, align, format!("{align:?}"));
}
});

View File

@@ -455,7 +455,7 @@ impl Tree {
.into_iter()
.enumerate()
.filter_map(|(i, mut tree)| {
if tree.ui_impl(ui, depth + 1, &format!("child #{}", i)) == Action::Keep {
if tree.ui_impl(ui, depth + 1, &format!("child #{i}")) == Action::Keep {
Some(tree)
} else {
None

View File

@@ -50,7 +50,7 @@ impl super::View for MultiTouch {
ui.label("Try touch gestures Pinch/Stretch, Rotation, and Pressure with 2+ fingers.");
let num_touches = ui.input(|i| i.multi_touch().map_or(0, |mt| mt.num_touches));
ui.label(format!("Current touches: {}", num_touches));
ui.label(format!("Current touches: {num_touches}"));
let color = if ui.visuals().dark_mode {
Color32::WHITE

View File

@@ -39,8 +39,8 @@ pub struct PlotDemo {
charts_demo: ChartsDemo,
items_demo: ItemsDemo,
interaction_demo: InteractionDemo,
custom_axes_demo: CustomAxisDemo,
linked_axes_demo: LinkedAxisDemo,
custom_axes_demo: CustomAxesDemo,
linked_axes_demo: LinkedAxesDemo,
open_panel: Panel,
}
@@ -326,7 +326,7 @@ impl MarkerDemo {
[5.0, 0.0 + y_offset],
[6.0, 0.5 + y_offset],
])
.name(format!("{:?}", marker))
.name(format!("{marker:?}"))
.filled(self.fill_markers)
.radius(self.marker_radius)
.shape(marker);
@@ -416,7 +416,7 @@ impl LegendDemo {
ui.label("Position:");
ui.horizontal(|ui| {
Corner::all().for_each(|position| {
ui.selectable_value(&mut config.position, position, format!("{:?}", position));
ui.selectable_value(&mut config.position, position, format!("{position:?}"));
});
});
ui.end_row();
@@ -448,19 +448,19 @@ impl LegendDemo {
// ----------------------------------------------------------------------------
#[derive(PartialEq, Default)]
struct CustomAxisDemo {}
struct CustomAxesDemo {}
impl CustomAxisDemo {
impl CustomAxesDemo {
const MINS_PER_DAY: f64 = 24.0 * 60.0;
const MINS_PER_H: f64 = 60.0;
fn logistic_fn() -> Line {
fn days(min: f64) -> f64 {
CustomAxisDemo::MINS_PER_DAY * min
CustomAxesDemo::MINS_PER_DAY * min
}
let values = PlotPoints::from_explicit_callback(
move |x| 1.0 / (1.0 + (-2.5 * (x / CustomAxisDemo::MINS_PER_DAY - 2.0)).exp()),
move |x| 1.0 / (1.0 + (-2.5 * (x / CustomAxesDemo::MINS_PER_DAY - 2.0)).exp()),
days(0.0)..days(5.0),
100,
);
@@ -504,8 +504,8 @@ impl CustomAxisDemo {
#[allow(clippy::unused_self)]
fn ui(&mut self, ui: &mut Ui) -> Response {
const MINS_PER_DAY: f64 = CustomAxisDemo::MINS_PER_DAY;
const MINS_PER_H: f64 = CustomAxisDemo::MINS_PER_H;
const MINS_PER_DAY: f64 = CustomAxesDemo::MINS_PER_DAY;
const MINS_PER_H: f64 = CustomAxesDemo::MINS_PER_H;
fn day(x: f64) -> f64 {
(x / MINS_PER_DAY).floor()
@@ -561,10 +561,10 @@ impl CustomAxisDemo {
.data_aspect(2.0 * MINS_PER_DAY as f32)
.x_axis_formatter(x_fmt)
.y_axis_formatter(y_fmt)
.x_grid_spacer(CustomAxisDemo::x_grid)
.x_grid_spacer(CustomAxesDemo::x_grid)
.label_formatter(label_fmt)
.show(ui, |plot_ui| {
plot_ui.line(CustomAxisDemo::logistic_fn());
plot_ui.line(CustomAxesDemo::logistic_fn());
})
.response
}
@@ -573,14 +573,14 @@ impl CustomAxisDemo {
// ----------------------------------------------------------------------------
#[derive(PartialEq)]
struct LinkedAxisDemo {
struct LinkedAxesDemo {
link_x: bool,
link_y: bool,
link_cursor_x: bool,
link_cursor_y: bool,
}
impl Default for LinkedAxisDemo {
impl Default for LinkedAxesDemo {
fn default() -> Self {
let link_x = true;
let link_y = false;
@@ -595,7 +595,7 @@ impl Default for LinkedAxisDemo {
}
}
impl LinkedAxisDemo {
impl LinkedAxesDemo {
fn line_with_slope(slope: f64) -> Line {
Line::new(PlotPoints::from_explicit_callback(
move |x| slope * x,
@@ -621,11 +621,11 @@ impl LinkedAxisDemo {
}
fn configure_plot(plot_ui: &mut plot::PlotUi) {
plot_ui.line(LinkedAxisDemo::line_with_slope(0.5));
plot_ui.line(LinkedAxisDemo::line_with_slope(1.0));
plot_ui.line(LinkedAxisDemo::line_with_slope(2.0));
plot_ui.line(LinkedAxisDemo::sin());
plot_ui.line(LinkedAxisDemo::cos());
plot_ui.line(LinkedAxesDemo::line_with_slope(0.5));
plot_ui.line(LinkedAxesDemo::line_with_slope(1.0));
plot_ui.line(LinkedAxesDemo::line_with_slope(2.0));
plot_ui.line(LinkedAxesDemo::sin());
plot_ui.line(LinkedAxesDemo::cos());
}
fn ui(&mut self, ui: &mut Ui) -> Response {
@@ -648,14 +648,14 @@ impl LinkedAxisDemo {
.height(250.0)
.link_axis(link_group_id, self.link_x, self.link_y)
.link_cursor(link_group_id, self.link_cursor_x, self.link_cursor_y)
.show(ui, LinkedAxisDemo::configure_plot);
.show(ui, LinkedAxesDemo::configure_plot);
Plot::new("linked_axis_2")
.data_aspect(2.0)
.width(150.0)
.height(250.0)
.link_axis(link_group_id, self.link_x, self.link_y)
.link_cursor(link_group_id, self.link_cursor_x, self.link_cursor_y)
.show(ui, LinkedAxisDemo::configure_plot);
.show(ui, LinkedAxesDemo::configure_plot);
});
Plot::new("linked_axis_3")
.data_aspect(0.5)
@@ -663,7 +663,7 @@ impl LinkedAxisDemo {
.height(150.0)
.link_axis(link_group_id, self.link_x, self.link_y)
.link_cursor(link_group_id, self.link_cursor_x, self.link_cursor_y)
.show(ui, LinkedAxisDemo::configure_plot)
.show(ui, LinkedAxesDemo::configure_plot)
.response
}
}
@@ -761,7 +761,7 @@ impl InteractionDemo {
plot_ui.pointer_coordinate(),
plot_ui.pointer_coordinate_drag_delta(),
plot_ui.plot_bounds(),
plot_ui.plot_hovered(),
plot_ui.response().hovered(),
)
});
@@ -774,21 +774,18 @@ impl InteractionDemo {
"origin in screen coordinates: x: {:.02}, y: {:.02}",
screen_pos.x, screen_pos.y
));
ui.label(format!("plot hovered: {}", hovered));
ui.label(format!("plot hovered: {hovered}"));
let coordinate_text = if let Some(coordinate) = pointer_coordinate {
format!("x: {:.02}, y: {:.02}", coordinate.x, coordinate.y)
} else {
"None".to_owned()
};
ui.label(format!("pointer coordinate: {}", coordinate_text));
ui.label(format!("pointer coordinate: {coordinate_text}"));
let coordinate_text = format!(
"x: {:.02}, y: {:.02}",
pointer_coordinate_drag_delta.x, pointer_coordinate_drag_delta.y
);
ui.label(format!(
"pointer coordinate drag delta: {}",
coordinate_text
));
ui.label(format!("pointer coordinate drag delta: {coordinate_text}"));
response
}

View File

@@ -232,10 +232,10 @@ impl super::View for ScrollTo {
for item in 1..=50 {
if track_item && item == self.track_item {
let response =
ui.colored_label(Color32::YELLOW, format!("This is item {}", item));
ui.colored_label(Color32::YELLOW, format!("This is item {item}"));
response.scroll_to_me(self.tack_item_align);
} else {
ui.label(format!("This is item {}", item));
ui.label(format!("This is item {item}"));
}
}
});
@@ -254,8 +254,7 @@ impl super::View for ScrollTo {
ui.separator();
ui.label(format!(
"Scroll offset: {:.0}/{:.0} px",
current_scroll, max_scroll
"Scroll offset: {current_scroll:.0}/{max_scroll:.0} px"
));
ui.separator();

View File

@@ -37,7 +37,7 @@ impl super::View for StripDemo {
.size(Size::exact(50.0))
.size(Size::remainder())
.size(Size::relative(0.5).at_least(60.0))
.size(Size::exact(10.0))
.size(Size::exact(10.5))
.vertical(|mut strip| {
strip.cell(|ui| {
ui.painter().rect_filled(

View File

@@ -38,7 +38,6 @@ impl super::Demo for TableDemo {
fn show(&mut self, ctx: &egui::Context, open: &mut bool) {
egui::Window::new(self.name())
.open(open)
.resizable(true)
.default_width(400.0)
.show(ctx, |ui| {
use super::View as _;
@@ -102,7 +101,7 @@ impl super::View for TableDemo {
use egui_extras::{Size, StripBuilder};
StripBuilder::new(ui)
.size(Size::remainder().at_least(100.0)) // for the table
.size(Size::exact(10.0)) // for the source code link
.size(Size::exact(10.5)) // for the source code link
.vertical(|mut strip| {
strip.cell(|ui| {
egui::ScrollArea::horizontal().show(ui, |ui| {

View File

@@ -20,7 +20,7 @@ impl super::View for CursorTest {
ui.heading("Hover to switch cursor icon:");
for &cursor_icon in &egui::CursorIcon::ALL {
let _ = ui
.button(format!("{:?}", cursor_icon))
.button(format!("{cursor_icon:?}"))
.on_hover_cursor(cursor_icon);
}
ui.add(crate::egui_github_link_file!());
@@ -239,7 +239,7 @@ impl super::View for TableTest {
for row in 0..self.num_rows {
for col in 0..self.num_cols {
if col == 0 {
ui.label(format!("row {}", row));
ui.label(format!("row {row}"));
} else {
let word_idx = row * 3 + col * 5;
let word_count = (row * 5 + col * 75) % 13;
@@ -350,13 +350,13 @@ impl super::View for InputTest {
use std::fmt::Write as _;
if response.clicked_by(button) {
writeln!(new_info, "Clicked by {:?} button", button).ok();
writeln!(new_info, "Clicked by {button:?} button").ok();
}
if response.double_clicked_by(button) {
writeln!(new_info, "Double-clicked by {:?} button", button).ok();
writeln!(new_info, "Double-clicked by {button:?} button").ok();
}
if response.triple_clicked_by(button) {
writeln!(new_info, "Triple-clicked by {:?} button", button).ok();
writeln!(new_info, "Triple-clicked by {button:?} button").ok();
}
if response.dragged_by(button) {
writeln!(

View File

@@ -126,7 +126,7 @@ impl WidgetGallery {
ui.add(doc_link_label("Hyperlink", "Hyperlink"));
use egui::special_emojis::GITHUB;
ui.hyperlink_to(
format!("{} egui on GitHub", GITHUB),
format!("{GITHUB} egui on GitHub"),
"https://github.com/emilk/egui",
);
ui.end_row();
@@ -173,7 +173,7 @@ impl WidgetGallery {
ui.add(doc_link_label("ComboBox", "ComboBox"));
egui::ComboBox::from_label("Take your pick")
.selected_text(format!("{:?}", radio))
.selected_text(format!("{radio:?}"))
.show_ui(ui, |ui| {
ui.style_mut().wrap = Some(false);
ui.set_min_width(60.0);
@@ -277,8 +277,8 @@ fn example_plot(ui: &mut egui::Ui) -> egui::Response {
}
fn doc_link_label<'a>(title: &'a str, search_term: &'a str) -> impl egui::Widget + 'a {
let label = format!("{}:", title);
let url = format!("https://docs.rs/egui?search={}", search_term);
let label = format!("{title}:");
let url = format!("https://docs.rs/egui?search={search_term}");
move |ui: &mut egui::Ui| {
ui.hyperlink_to(label, url).on_hover_ui(|ui| {
ui.horizontal_wrapped(|ui| {

View File

@@ -161,7 +161,7 @@ fn numbered_point(ui: &mut Ui, width: f32, number: &str) -> Response {
let font_id = TextStyle::Body.resolve(ui.style());
let row_height = ui.fonts(|f| f.row_height(&font_id));
let (rect, response) = ui.allocate_exact_size(vec2(width, row_height), Sense::hover());
let text = format!("{}.", number);
let text = format!("{number}.");
let text_color = ui.visuals().strong_text_color();
ui.painter().text(
rect.right_center(),

View File

@@ -1,8 +1,8 @@
# Changelog for egui_extras
All notable changes to the `egui_extras` integration will be noted in this file.
## Unreleased
This file is updated upon each release.
Changes since the last release can be found by running the `scripts/generate_changelog.py` script.
## 0.22.0 - 2023-05-23

View File

@@ -8,7 +8,7 @@ authors = [
]
description = "Extra functionality and widgets for the egui GUI library"
edition = "2021"
rust-version = "1.65"
rust-version = "1.67"
homepage = "https://github.com/emilk/egui"
license = "MIT OR Apache-2.0"
readme = "README.md"

View File

@@ -428,6 +428,6 @@ fn month_name(i: u32) -> &'static str {
10 => "October",
11 => "November",
12 => "December",
_ => panic!("Unknown month: {}", i),
_ => panic!("Unknown month: {i}"),
}
}

View File

@@ -10,11 +10,15 @@ pub use usvg::FitTo;
/// Use the `svg` and `image` features to enable more constructors.
pub struct RetainedImage {
debug_name: String,
size: [usize; 2],
/// Cleared once [`Self::texture`] has been loaded.
image: Mutex<egui::ColorImage>,
/// Lazily loaded when we have an egui context.
texture: Mutex<Option<egui::TextureHandle>>,
options: TextureOptions,
}
@@ -254,7 +258,7 @@ pub fn load_svg_bytes_with_size(
};
let mut pixmap = tiny_skia::Pixmap::new(w, h)
.ok_or_else(|| format!("Failed to create SVG Pixmap of size {}x{}", w, h))?;
.ok_or_else(|| format!("Failed to create SVG Pixmap of size {w}x{h}"))?;
resvg::render(&rtree, fit_to, Default::default(), pixmap.as_mut())
.ok_or_else(|| "Failed to render SVG".to_owned())?;

View File

@@ -32,9 +32,11 @@ pub struct StripLayout<'l> {
direction: CellDirection,
pub(crate) rect: Rect,
pub(crate) cursor: Pos2,
/// Keeps track of the max used position,
/// so we know how much space we used.
max: Pos2,
cell_layout: egui::Layout,
}

View File

@@ -1,14 +1,16 @@
use egui::Rangef;
/// Size hint for table column/strip cell.
#[derive(Clone, Debug, Copy)]
pub enum Size {
/// Absolute size in points, with a given range of allowed sizes to resize within.
Absolute { initial: f32, range: (f32, f32) },
Absolute { initial: f32, range: Rangef },
/// Relative size relative to all available space.
Relative { fraction: f32, range: (f32, f32) },
Relative { fraction: f32, range: Rangef },
/// Multiple remainders each get the same space.
Remainder { range: (f32, f32) },
Remainder { range: Rangef },
}
impl Size {
@@ -16,7 +18,7 @@ impl Size {
pub fn exact(points: f32) -> Self {
Self::Absolute {
initial: points,
range: (points, points),
range: Rangef::new(points, points),
}
}
@@ -24,7 +26,7 @@ impl Size {
pub fn initial(points: f32) -> Self {
Self::Absolute {
initial: points,
range: (0.0, f32::INFINITY),
range: Rangef::new(0.0, f32::INFINITY),
}
}
@@ -33,14 +35,14 @@ impl Size {
egui::egui_assert!(0.0 <= fraction && fraction <= 1.0);
Self::Relative {
fraction,
range: (0.0, f32::INFINITY),
range: Rangef::new(0.0, f32::INFINITY),
}
}
/// Multiple remainders each get the same space.
pub fn remainder() -> Self {
Self::Remainder {
range: (0.0, f32::INFINITY),
range: Rangef::new(0.0, f32::INFINITY),
}
}
@@ -50,7 +52,7 @@ impl Size {
Self::Absolute { range, .. }
| Self::Relative { range, .. }
| Self::Remainder { range, .. } => {
range.0 = minimum;
range.min = minimum;
}
}
self
@@ -62,14 +64,14 @@ impl Size {
Self::Absolute { range, .. }
| Self::Relative { range, .. }
| Self::Remainder { range, .. } => {
range.1 = maximum;
range.max = maximum;
}
}
self
}
/// Allowed range of movement (in points), if in a resizable [`Table`](crate::table::Table).
pub fn range(self) -> (f32, f32) {
pub fn range(self) -> Rangef {
match self {
Self::Absolute { range, .. }
| Self::Relative { range, .. }
@@ -99,12 +101,9 @@ impl Sizing {
.iter()
.map(|&size| match size {
Size::Absolute { initial, .. } => initial,
Size::Relative {
fraction,
range: (min, max),
} => {
Size::Relative { fraction, range } => {
assert!(0.0 <= fraction && fraction <= 1.0);
(length * fraction).clamp(min, max)
range.clamp(length * fraction)
}
Size::Remainder { .. } => {
remainders += 1;
@@ -120,9 +119,9 @@ impl Sizing {
let mut remainder_length = length - sum_non_remainder;
let avg_remainder_length = 0.0f32.max(remainder_length / remainders as f32).floor();
self.sizes.iter().for_each(|&size| {
if let Size::Remainder { range: (min, _max) } = size {
if avg_remainder_length < min {
remainder_length -= min;
if let Size::Remainder { range } = size {
if avg_remainder_length < range.min {
remainder_length -= range.min;
remainders -= 1;
}
}
@@ -138,11 +137,8 @@ impl Sizing {
.iter()
.map(|&size| match size {
Size::Absolute { initial, .. } => initial,
Size::Relative {
fraction,
range: (min, max),
} => (length * fraction).clamp(min, max),
Size::Remainder { range: (min, max) } => avg_remainder_length.clamp(min, max),
Size::Relative { fraction, range } => range.clamp(length * fraction),
Size::Remainder { range } => range.clamp(avg_remainder_length),
})
.collect()
}

View File

@@ -72,13 +72,13 @@ impl<'a> StripBuilder<'a> {
self
}
/// Allocate space for for one column/row.
/// Allocate space for one column/row.
pub fn size(mut self, size: Size) -> Self {
self.sizing.add(size);
self
}
/// Allocate space for for several columns/rows at once.
/// Allocate space for several columns/rows at once.
pub fn sizes(mut self, size: Size, count: usize) -> Self {
for _ in 0..count {
self.sizing.add(size);

View File

@@ -3,7 +3,7 @@
//! | fixed size | all available space/minimum | 30% of available width | fixed size |
//! Takes all available height, so if you want something below the table, put it in a strip.
use egui::{Align, NumExt as _, Rect, Response, ScrollArea, Ui, Vec2};
use egui::{Align, NumExt as _, Rangef, Rect, Response, ScrollArea, Ui, Vec2};
use crate::{
layout::{CellDirection, CellSize},
@@ -28,7 +28,9 @@ enum InitialColumnSize {
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct Column {
initial_width: InitialColumnSize,
width_range: (f32, f32),
width_range: Rangef,
/// Clip contents if too narrow?
clip: bool,
@@ -78,7 +80,7 @@ impl Column {
fn new(initial_width: InitialColumnSize) -> Self {
Self {
initial_width,
width_range: (0.0, f32::INFINITY),
width_range: Rangef::new(0.0, f32::INFINITY),
resizable: None,
clip: false,
}
@@ -110,7 +112,7 @@ impl Column {
///
/// Default: 0.0
pub fn at_least(mut self, minimum: f32) -> Self {
self.width_range.0 = minimum;
self.width_range.min = minimum;
self
}
@@ -118,13 +120,13 @@ impl Column {
///
/// Default: [`f32::INFINITY`]
pub fn at_most(mut self, maximum: f32) -> Self {
self.width_range.1 = maximum;
self.width_range.max = maximum;
self
}
/// Allowed range of movement (in points), if in a resizable [`Table`](crate::table::Table).
pub fn range(mut self, range: std::ops::RangeInclusive<f32>) -> Self {
self.width_range = (*range.start(), *range.end());
pub fn range(mut self, range: impl Into<Rangef>) -> Self {
self.width_range = range.into();
self
}
@@ -146,8 +148,8 @@ fn to_sizing(columns: &[Column]) -> crate::sizing::Sizing {
InitialColumnSize::Automatic(suggested_width) => Size::initial(suggested_width),
InitialColumnSize::Remainder => Size::remainder(),
}
.at_least(column.width_range.0)
.at_most(column.width_range.1);
.at_least(column.width_range.min)
.at_most(column.width_range.max);
sizing.add(size);
}
sizing
@@ -511,8 +513,10 @@ pub struct Table<'a> {
columns: Vec<Column>,
available_width: f32,
state: TableState,
/// Accumulated maximum used widths for each column.
max_used_widths: Vec<f32>,
first_frame_auto_size_columns: bool,
resizable: bool,
striped: bool,
@@ -598,13 +602,13 @@ impl<'a> Table<'a> {
if scroll_to_row.is_some() && scroll_to_y_range.is_none() {
// TableBody::row didn't find the right row, so scroll to the bottom:
scroll_to_y_range = Some((f32::INFINITY, f32::INFINITY));
scroll_to_y_range = Some(Rangef::new(f32::INFINITY, f32::INFINITY));
}
});
if let Some((min_y, max_y)) = scroll_to_y_range {
if let Some(y_range) = scroll_to_y_range {
let x = 0.0; // ignored, we only have vertical scrolling
let rect = egui::Rect::from_min_max(egui::pos2(x, min_y), egui::pos2(x, max_y));
let rect = egui::Rect::from_x_y_ranges(x..=x, y_range);
let align = scroll_to_row.and_then(|(_, a)| a);
ui.scroll_to_rect(rect, align);
}
@@ -617,14 +621,14 @@ impl<'a> Table<'a> {
for (i, column_width) in state.column_widths.iter_mut().enumerate() {
let column = &columns[i];
let column_is_resizable = column.resizable.unwrap_or(resizable);
let (min_width, max_width) = column.width_range;
let width_range = column.width_range;
if !column.clip {
// Unless we clip we don't want to shrink below the
// size that was actually used:
*column_width = column_width.at_least(max_used_widths[i]);
}
*column_width = column_width.clamp(min_width, max_width);
*column_width = width_range.clamp(*column_width);
let is_last_column = i + 1 == columns.len();
@@ -633,7 +637,7 @@ impl<'a> Table<'a> {
let eps = 0.1; // just to avoid some rounding errors.
*column_width = available_width - eps;
*column_width = column_width.at_least(max_used_widths[i]);
*column_width = column_width.clamp(min_width, max_width);
*column_width = width_range.clamp(*column_width);
break;
}
@@ -641,7 +645,7 @@ impl<'a> Table<'a> {
if column.is_auto() && (first_frame_auto_size_columns || !column_is_resizable) {
*column_width = max_used_widths[i];
*column_width = column_width.clamp(min_width, max_width);
*column_width = width_range.clamp(*column_width);
} else if column_is_resizable {
let column_resize_id = ui.id().with("resize_column").with(i);
@@ -656,7 +660,7 @@ impl<'a> Table<'a> {
if resize_response.double_clicked() {
// Resize to the minimum of what is needed.
*column_width = max_used_widths[i].clamp(min_width, max_width);
*column_width = width_range.clamp(max_used_widths[i]);
} else if resize_response.dragged() {
if let Some(pointer) = ui.ctx().pointer_latest_pos() {
let mut new_width = *column_width + pointer.x - x;
@@ -671,7 +675,7 @@ impl<'a> Table<'a> {
new_width =
new_width.at_least(max_used_widths[i] - max_shrinkage_per_frame);
}
new_width = new_width.clamp(min_width, max_width);
new_width = width_range.clamp(new_width);
let x = x - *column_width + new_width;
(p0.x, p1.x) = (x, x);
@@ -731,7 +735,7 @@ pub struct TableBody<'a> {
/// If we find the correct row to scroll to,
/// this is set to the y-range of the row.
scroll_to_y_range: &'a mut Option<(f32, f32)>,
scroll_to_y_range: &'a mut Option<Rangef>,
}
impl<'a> TableBody<'a> {
@@ -779,7 +783,7 @@ impl<'a> TableBody<'a> {
let bottom_y = self.layout.cursor.y;
if Some(self.row_nr) == self.scroll_to_row {
*self.scroll_to_y_range = Some((top_y, bottom_y));
*self.scroll_to_y_range = Some(Rangef::new(top_y, bottom_y));
}
self.row_nr += 1;
@@ -819,7 +823,7 @@ impl<'a> TableBody<'a> {
if let Some(scroll_to_row) = self.scroll_to_row {
let scroll_to_row = scroll_to_row.at_most(total_rows.saturating_sub(1)) as f32;
*self.scroll_to_y_range = Some((
*self.scroll_to_y_range = Some(Rangef::new(
self.layout.cursor.y + scroll_to_row * row_height_with_spacing,
self.layout.cursor.y + (scroll_to_row + 1.0) * row_height_with_spacing,
));
@@ -909,7 +913,7 @@ impl<'a> TableBody<'a> {
cursor_y += (row_height + spacing.y) as f64;
if Some(row_index) == self.scroll_to_row {
*self.scroll_to_y_range = Some((
*self.scroll_to_y_range = Some(Rangef::new(
(scroll_to_y_range_offset + old_cursor_y) as f32,
(scroll_to_y_range_offset + cursor_y) as f32,
));
@@ -953,7 +957,7 @@ impl<'a> TableBody<'a> {
cursor_y += (row_height + spacing.y) as f64;
if Some(row_index) == self.scroll_to_row {
*self.scroll_to_y_range = Some((
*self.scroll_to_y_range = Some(Rangef::new(
(scroll_to_y_range_offset + top_y) as f32,
(scroll_to_y_range_offset + cursor_y) as f32,
));
@@ -972,7 +976,7 @@ impl<'a> TableBody<'a> {
let top_y = cursor_y;
cursor_y += (row_height + spacing.y) as f64;
if Some(row_index) == self.scroll_to_row {
*self.scroll_to_y_range = Some((
*self.scroll_to_y_range = Some(Rangef::new(
(scroll_to_y_range_offset + top_y) as f32,
(scroll_to_y_range_offset + cursor_y) as f32,
));
@@ -981,10 +985,8 @@ impl<'a> TableBody<'a> {
if self.scroll_to_row.is_some() && self.scroll_to_y_range.is_none() {
// Catch desire to scroll past the end:
*self.scroll_to_y_range = Some((
(scroll_to_y_range_offset + cursor_y) as f32,
(scroll_to_y_range_offset + cursor_y) as f32,
));
*self.scroll_to_y_range =
Some(Rangef::point((scroll_to_y_range_offset + cursor_y) as f32));
}
if height_below_visible > 0.0 {
@@ -1013,8 +1015,10 @@ pub struct TableRow<'a, 'b> {
layout: &'b mut StripLayout<'a>,
columns: &'b [Column],
widths: &'b [f32],
/// grows during building with the maximum widths
max_used_widths: &'b mut [f32],
col_index: usize,
striped: bool,
height: f32,

View File

@@ -1,6 +1,9 @@
# Changelog for egui_glium
All notable changes to the `egui_glium` integration will be noted in this file.
This file is updated upon each release.
Changes since the last release can be found by running the `scripts/generate_changelog.py` script.
## Unreleased
* Remove the `screen_reader` feature ([#2669](https://github.com/emilk/egui/pull/2669)).

View File

@@ -4,7 +4,7 @@ version = "0.22.0"
authors = ["Emil Ernerfeldt <emil.ernerfeldt@gmail.com>"]
description = "Bindings for using egui natively using the glium library"
edition = "2021"
rust-version = "1.65"
rust-version = "1.67"
homepage = "https://github.com/emilk/egui/tree/master/crates/egui_glium"
license = "MIT OR Apache-2.0"
readme = "README.md"

View File

@@ -1,8 +1,8 @@
# Changelog for egui_glow
All notable changes to the `egui_glow` integration will be noted in this file.
## Unreleased
This file is updated upon each release.
Changes since the last release can be found by running the `scripts/generate_changelog.py` script.
## 0.22.0 - 2023-05-23

View File

@@ -4,7 +4,7 @@ version = "0.22.0"
authors = ["Emil Ernerfeldt <emil.ernerfeldt@gmail.com>"]
description = "Bindings for using egui natively using the glow library"
edition = "2021"
rust-version = "1.65"
rust-version = "1.67"
homepage = "https://github.com/emilk/egui/tree/master/crates/egui_glow"
license = "MIT OR Apache-2.0"
readme = "README.md"

View File

@@ -4,7 +4,7 @@ version = "0.22.0"
authors = ["Emil Ernerfeldt <emil.ernerfeldt@gmail.com>"]
description = "Minimal 2D math library for GUI work"
edition = "2021"
rust-version = "1.65"
rust-version = "1.67"
homepage = "https://github.com/emilk/egui/tree/master/crates/emath"
license = "MIT OR Apache-2.0"
readme = "README.md"

View File

@@ -110,29 +110,25 @@ impl Align {
/// assert_eq!(Max .align_size_within_range(INFINITY, NEG_INFINITY..=20.0), NEG_INFINITY..=20.0);
/// ```
#[inline]
pub fn align_size_within_range(
self,
size: f32,
range: RangeInclusive<f32>,
) -> RangeInclusive<f32> {
let min = *range.start();
let max = *range.end();
pub fn align_size_within_range(self, size: f32, range: impl Into<Rangef>) -> Rangef {
let range = range.into();
let Rangef { min, max } = range;
if max - min == f32::INFINITY && size == f32::INFINITY {
return range;
}
match self {
Self::Min => min..=min + size,
Self::Min => Rangef::new(min, min + size),
Self::Center => {
if size == f32::INFINITY {
f32::NEG_INFINITY..=f32::INFINITY
Rangef::new(f32::NEG_INFINITY, f32::INFINITY)
} else {
let left = (min + max) / 2.0 - size / 2.0;
left..=left + size
Rangef::new(left, left + size)
}
}
Self::Max => max - size..=max,
Self::Max => Rangef::new(max - size, max),
}
}
}

View File

@@ -99,11 +99,12 @@ impl Real for f64 {}
/// assert_eq!(lerp(1.0..=5.0, 2.0), 9.0);
/// ```
#[inline(always)]
pub fn lerp<R, T>(range: RangeInclusive<R>, t: T) -> R
pub fn lerp<R, T>(range: impl Into<RangeInclusive<R>>, t: T) -> R
where
T: Real + Mul<R, Output = R>,
R: Copy + Add<R, Output = R>,
{
let range = range.into();
(T::one() - t) * *range.start() + t * *range.end()
}
@@ -138,20 +139,28 @@ where
/// Linearly remap a value from one range to another,
/// so that when `x == from.start()` returns `to.start()`
/// and when `x == from.end()` returns `to.end()`.
pub fn remap<T>(x: T, from: RangeInclusive<T>, to: RangeInclusive<T>) -> T
pub fn remap<T>(x: T, from: impl Into<RangeInclusive<T>>, to: impl Into<RangeInclusive<T>>) -> T
where
T: Real,
{
let from = from.into();
let to = to.into();
crate::emath_assert!(from.start() != from.end());
let t = (x - *from.start()) / (*from.end() - *from.start());
lerp(to, t)
}
/// Like [`remap`], but also clamps the value so that the returned value is always in the `to` range.
pub fn remap_clamp<T>(x: T, from: RangeInclusive<T>, to: RangeInclusive<T>) -> T
pub fn remap_clamp<T>(
x: T,
from: impl Into<RangeInclusive<T>>,
to: impl Into<RangeInclusive<T>>,
) -> T
where
T: Real,
{
let from = from.into();
let to = to.into();
if from.end() < from.start() {
return remap_clamp(x, *from.end()..=*from.start(), *to.end()..=*to.start());
}
@@ -174,9 +183,7 @@ where
/// Round a value to the given number of decimal places.
pub fn round_to_decimals(value: f64, decimal_places: usize) -> f64 {
// This is a stupid way of doing this, but stupid works.
format!("{:.*}", decimal_places, value)
.parse()
.unwrap_or(value)
format!("{value:.decimal_places$}").parse().unwrap_or(value)
}
pub fn format_with_minimum_decimals(value: f64, decimals: usize) -> String {
@@ -194,7 +201,7 @@ pub fn format_with_decimals_in_range(value: f64, decimal_range: RangeInclusive<u
if min_decimals != max_decimals {
// Ugly/slow way of doing this. TODO(emilk): clean up precision.
for decimals in min_decimals..max_decimals {
let text = format!("{:.*}", decimals, value);
let text = format!("{value:.decimals$}");
let epsilon = 16.0 * f32::EPSILON; // margin large enough to handle most peoples round-tripping needs
if almost_equal(text.parse::<f32>().unwrap(), value as f32, epsilon) {
// Enough precision to show the value accurately - good!
@@ -205,7 +212,7 @@ pub fn format_with_decimals_in_range(value: f64, decimal_range: RangeInclusive<u
// Probably the value was set not by the slider, but from outside.
// In any case: show the full value
}
format!("{:.*}", max_decimals, value)
format!("{value:.max_decimals$}")
}
/// Return true when arguments are the same within some rounding error.

View File

@@ -206,7 +206,7 @@ impl std::ops::Index<usize> for Pos2 {
match index {
0 => &self.x,
1 => &self.y,
_ => panic!("Pos2 index out of bounds: {}", index),
_ => panic!("Pos2 index out of bounds: {index}"),
}
}
}
@@ -217,7 +217,7 @@ impl std::ops::IndexMut<usize> for Pos2 {
match index {
0 => &mut self.x,
1 => &mut self.y,
_ => panic!("Pos2 index out of bounds: {}", index),
_ => panic!("Pos2 index out of bounds: {index}"),
}
}
}

View File

@@ -36,14 +36,60 @@ impl Rangef {
}
#[inline]
pub fn span(&self) -> f32 {
pub fn point(min_and_max: f32) -> Self {
Self {
min: min_and_max,
max: min_and_max,
}
}
/// The length of the range, i.e. `max - min`.
#[inline]
pub fn span(self) -> f32 {
self.max - self.min
}
#[inline]
pub fn contains(&self, x: f32) -> bool {
#[must_use]
pub fn contains(self, x: f32) -> bool {
self.min <= x && x <= self.max
}
/// Equivalent to `x.clamp(min, max)`
#[inline]
#[must_use]
pub fn clamp(self, x: f32) -> f32 {
x.clamp(self.min, self.max)
}
/// Flip `min` and `max` if needed, so that `min <= max` after.
#[inline]
pub fn as_positive(self) -> Self {
Rangef {
min: self.min.min(self.max),
max: self.min.max(self.max),
}
}
/// Shrink by this much on each side, keeping the center
#[inline]
#[must_use]
pub fn shrink(self, amnt: f32) -> Self {
Self {
min: self.min + amnt,
max: self.max - amnt,
}
}
/// Expand by this much on each side, keeping the center
#[inline]
#[must_use]
pub fn expand(self, amnt: f32) -> Self {
Self {
min: self.min - amnt,
max: self.max + amnt,
}
}
}
impl From<Rangef> for RangeInclusive<f32> {
@@ -108,3 +154,17 @@ impl From<RangeToInclusive<f32>> for Rangef {
Self::new(f32::NEG_INFINITY, range.end)
}
}
impl PartialEq<RangeInclusive<f32>> for Rangef {
#[inline]
fn eq(&self, other: &RangeInclusive<f32>) -> bool {
self.min == *other.start() && self.max == *other.end()
}
}
impl PartialEq<Rangef> for RangeInclusive<f32> {
#[inline]
fn eq(&self, other: &Rangef) -> bool {
*self.start() == other.min && *self.end() == other.max
}
}

View File

@@ -1,5 +1,4 @@
use std::f32::INFINITY;
use std::ops::RangeInclusive;
use crate::*;
@@ -82,15 +81,12 @@ impl Rect {
}
#[inline(always)]
pub fn from_x_y_ranges(
x_range: impl Into<RangeInclusive<f32>>,
y_range: impl Into<RangeInclusive<f32>>,
) -> Self {
pub fn from_x_y_ranges(x_range: impl Into<Rangef>, y_range: impl Into<Rangef>) -> Self {
let x_range = x_range.into();
let y_range = y_range.into();
Rect {
min: pos2(*x_range.start(), *y_range.start()),
max: pos2(*x_range.end(), *y_range.end()),
min: pos2(x_range.min, y_range.min),
max: pos2(x_range.max, y_range.max),
}
}
@@ -280,6 +276,7 @@ impl Rect {
}
}
/// `rect.size() == Vec2 { x: rect.width(), y: rect.height() }`
#[inline(always)]
pub fn size(&self) -> Vec2 {
self.max - self.min
@@ -389,18 +386,18 @@ impl Rect {
}
#[inline(always)]
pub fn x_range(&self) -> RangeInclusive<f32> {
self.min.x..=self.max.x
pub fn x_range(&self) -> Rangef {
Rangef::new(self.min.x, self.max.x)
}
#[inline(always)]
pub fn y_range(&self) -> RangeInclusive<f32> {
self.min.y..=self.max.y
pub fn y_range(&self) -> Rangef {
Rangef::new(self.min.y, self.max.y)
}
#[inline(always)]
pub fn bottom_up_range(&self) -> RangeInclusive<f32> {
self.max.y..=self.min.y
pub fn bottom_up_range(&self) -> Rangef {
Rangef::new(self.max.y, self.min.y)
}
/// `width < 0 || height < 0`

View File

@@ -184,10 +184,7 @@ mod test {
let expected = vec2(0.0, 3.0);
assert!(
(rotated - expected).length() < 1e-5,
"Expected {:?} to equal {:?}. rot: {:?}",
rotated,
expected,
rot,
"Expected {rotated:?} to equal {expected:?}. rot: {rot:?}",
);
let undone = rot.inverse() * rot;

View File

@@ -292,7 +292,7 @@ impl std::ops::Index<usize> for Vec2 {
match index {
0 => &self.x,
1 => &self.y,
_ => panic!("Vec2 index out of bounds: {}", index),
_ => panic!("Vec2 index out of bounds: {index}"),
}
}
}
@@ -303,7 +303,7 @@ impl std::ops::IndexMut<usize> for Vec2 {
match index {
0 => &mut self.x,
1 => &mut self.y,
_ => panic!("Vec2 index out of bounds: {}", index),
_ => panic!("Vec2 index out of bounds: {index}"),
}
}
}

View File

@@ -1,8 +1,9 @@
# epaint changelog
All notable changes to the epaint crate will be documented in this file.
This file is updated upon each release.
Changes since the last release can be found by running the `scripts/generate_changelog.py` script.
## Unreleased
## 0.22.0 - 2023-05-23

View File

@@ -4,7 +4,7 @@ version = "0.22.0"
authors = ["Emil Ernerfeldt <emil.ernerfeldt@gmail.com>"]
description = "Minimal 2D graphics library for GUI work"
edition = "2021"
rust-version = "1.65"
rust-version = "1.67"
homepage = "https://github.com/emilk/egui/tree/master/crates/epaint"
license = "(MIT OR Apache-2.0) AND OFL-1.1 AND LicenseRef-UFL-1.0" # OFL and UFL used by default_fonts. See https://github.com/emilk/egui/issues/2321
readme = "README.md"
@@ -79,6 +79,7 @@ ahash = { version = "0.8.1", default-features = false, features = [
"std",
] }
nohash-hasher = "0.2"
parking_lot = "0.12" # Using parking_lot over std::sync::Mutex gives 50% speedups in some real-world scenarios.
#! ### Optional dependencies
bytemuck = { version = "1.7.2", optional = true, features = ["derive"] }
@@ -94,11 +95,6 @@ serde = { version = "1", optional = true, features = ["derive", "rc"] }
# native:
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
backtrace = { version = "0.3", optional = true }
parking_lot = "0.12" # Using parking_lot over std::sync::Mutex gives 50% speedups in some real-world scenarios.
# web:
[target.'cfg(target_arch = "wasm32")'.dependencies]
atomic_refcell = "0.1" # Used instead of parking_lot on on wasm. See https://github.com/emilk/egui/issues/1401
[dev-dependencies]

View File

@@ -47,7 +47,7 @@ fn tessellate_circles(c: &mut Criterion) {
for _ in 0..10_000 {
let clip_rect = Rect::from_min_size(Pos2::ZERO, Vec2::splat(1024.0));
let shape = Shape::circle_filled(Pos2::new(10.0, 10.0), r, Color32::WHITE);
clipped_shapes.push(ClippedShape(clip_rect, shape));
clipped_shapes.push(ClippedShape { clip_rect, shape });
}
}
assert_eq!(clipped_shapes.len(), 100_000);

View File

@@ -110,6 +110,15 @@ impl ColorImage {
Self { size, pixels }
}
/// Create a [`ColorImage`] from flat opaque gray data.
///
/// Panics if `size[0] * size[1] != gray.len()`.
pub fn from_gray(size: [usize; 2], gray: &[u8]) -> Self {
assert_eq!(size[0] * size[1], gray.len());
let pixels = gray.iter().map(|p| Color32::from_gray(*p)).collect();
Self { size, pixels }
}
/// A view of the underlying data as `&[u8]`
#[cfg(feature = "bytemuck")]
pub fn as_raw(&self) -> &[u8] {

View File

@@ -90,13 +90,14 @@ impl Default for TextureId {
///
/// Everything is using logical points.
#[derive(Clone, Debug, PartialEq)]
pub struct ClippedShape(
pub struct ClippedShape {
/// Clip / scissor rectangle.
/// Only show the part of the [`Shape`] that falls within this.
pub emath::Rect,
pub clip_rect: emath::Rect,
/// The shape
pub Shape,
);
pub shape: Shape,
}
/// A [`Mesh`] or [`PaintCallback`] within a clip rectangle.
///

View File

@@ -251,8 +251,7 @@ impl Mesh {
assert!(
index_cursor > span_start,
"One triangle spanned more than {} vertices",
MAX_SIZE
"One triangle spanned more than {MAX_SIZE} vertices"
);
let mesh = Mesh16 {

View File

@@ -1,13 +1,14 @@
//! Helper module that wraps some Mutex types with different implementations.
//! Helper module that adds extra checks when the `deadlock_detection` feature is turned on.
// ----------------------------------------------------------------------------
#[cfg(not(target_arch = "wasm32"))]
#[cfg(not(debug_assertions))]
#[cfg(not(feature = "deadlock_detection"))]
mod mutex_impl {
/// Provides interior mutability.
///
/// Uses `parking_lot` crate on native targets, and `atomic_refcell` on `wasm32` targets.
/// This is a thin wrapper around [`parking_lot::Mutex`], except if
/// the feature `deadlock_detection` is turned enabled, in which case
/// extra checks are added to detect deadlocks.
#[derive(Default)]
pub struct Mutex<T>(parking_lot::Mutex<T>);
@@ -27,12 +28,13 @@ mod mutex_impl {
}
}
#[cfg(not(target_arch = "wasm32"))]
#[cfg(debug_assertions)]
#[cfg(feature = "deadlock_detection")]
mod mutex_impl {
/// Provides interior mutability.
///
/// Uses `parking_lot` crate on native targets, and `atomic_refcell` on `wasm32` targets.
/// This is a thin wrapper around [`parking_lot::Mutex`], except if
/// the feature `deadlock_detection` is turned enabled, in which case
/// extra checks are added to detect deadlocks.
#[derive(Default)]
pub struct Mutex<T>(parking_lot::Mutex<T>);
@@ -82,6 +84,11 @@ mod mutex_impl {
MutexGuard(self.0.lock(), ptr)
}
#[inline(always)]
pub fn into_inner(self) -> T {
self.0.into_inner()
}
}
impl<T> Drop for MutexGuard<'_, T> {
@@ -110,7 +117,8 @@ mod mutex_impl {
}
}
#[cfg(not(target_arch = "wasm32"))]
// ----------------------------------------------------------------------------
#[cfg(not(feature = "deadlock_detection"))]
mod rw_lock_impl {
/// The lock you get from [`RwLock::read`].
@@ -121,7 +129,9 @@ mod rw_lock_impl {
/// Provides interior mutability.
///
/// Uses `parking_lot` crate on native targets, and `atomic_refcell` on `wasm32` targets.
/// This is a thin wrapper around [`parking_lot::RwLock`], except if
/// the feature `deadlock_detection` is turned enabled, in which case
/// extra checks are added to detect deadlocks.
#[derive(Default)]
pub struct RwLock<T>(parking_lot::RwLock<T>);
@@ -143,7 +153,6 @@ mod rw_lock_impl {
}
}
#[cfg(not(target_arch = "wasm32"))]
#[cfg(feature = "deadlock_detection")]
mod rw_lock_impl {
use std::{
@@ -241,7 +250,9 @@ mod rw_lock_impl {
/// Provides interior mutability.
///
/// Uses `parking_lot` crate on native targets, and `atomic_refcell` on `wasm32` targets.
/// This is a thin wrapper around [`parking_lot::RwLock`], except if
/// the feature `deadlock_detection` is turned enabled, in which case
/// extra checks are added to detect deadlocks.
#[derive(Default)]
pub struct RwLock<T> {
lock: parking_lot::RwLock<T>,
@@ -314,6 +325,11 @@ mod rw_lock_impl {
holders: Arc::clone(&self.holders),
}
}
#[inline(always)]
pub fn into_inner(self) -> T {
self.lock.into_inner()
}
}
fn make_backtrace() -> backtrace::Backtrace {
@@ -323,7 +339,7 @@ mod rw_lock_impl {
fn format_backtrace(backtrace: &mut backtrace::Backtrace) -> String {
backtrace.resolve();
let stacktrace = format!("{:?}", backtrace);
let stacktrace = format!("{backtrace:?}");
// Remove irrelevant parts of the stacktrace:
let end_offset = stacktrace
@@ -342,70 +358,6 @@ mod rw_lock_impl {
// ----------------------------------------------------------------------------
#[cfg(target_arch = "wasm32")]
mod mutex_impl {
// `atomic_refcell` will panic if multiple threads try to access the same value
/// Provides interior mutability.
///
/// Uses `parking_lot` crate on native targets, and `atomic_refcell` on `wasm32` targets.
#[derive(Default)]
pub struct Mutex<T>(atomic_refcell::AtomicRefCell<T>);
/// The lock you get from [`Mutex`].
pub use atomic_refcell::AtomicRefMut as MutexGuard;
impl<T> Mutex<T> {
#[inline(always)]
pub fn new(val: T) -> Self {
Self(atomic_refcell::AtomicRefCell::new(val))
}
/// Panics if already locked.
#[inline(always)]
pub fn lock(&self) -> MutexGuard<'_, T> {
self.0.borrow_mut()
}
}
}
#[cfg(target_arch = "wasm32")]
mod rw_lock_impl {
// `atomic_refcell` will panic if multiple threads try to access the same value
/// The lock you get from [`RwLock::read`].
pub use atomic_refcell::AtomicRef as RwLockReadGuard;
/// The lock you get from [`RwLock::write`].
pub use atomic_refcell::AtomicRefMut as RwLockWriteGuard;
/// Provides interior mutability.
///
/// Uses `parking_lot` crate on native targets, and `atomic_refcell` on `wasm32` targets.
#[derive(Default)]
pub struct RwLock<T>(atomic_refcell::AtomicRefCell<T>);
impl<T> RwLock<T> {
#[inline(always)]
pub fn new(val: T) -> Self {
Self(atomic_refcell::AtomicRefCell::new(val))
}
#[inline(always)]
pub fn read(&self) -> RwLockReadGuard<'_, T> {
self.0.borrow()
}
/// Panics if already locked.
#[inline(always)]
pub fn write(&self) -> RwLockWriteGuard<'_, T> {
self.0.borrow_mut()
}
}
}
// ----------------------------------------------------------------------------
pub use mutex_impl::{Mutex, MutexGuard};
pub use rw_lock_impl::{RwLock, RwLockReadGuard, RwLockWriteGuard};
@@ -449,7 +401,7 @@ mod tests {
let other_thread = {
let one = Arc::clone(&one);
std::thread::spawn(move || {
let _ = one.lock();
let _lock = one.lock();
})
};
std::thread::sleep(Duration::from_millis(200));

View File

@@ -1,6 +1,5 @@
//! The different shapes that can be painted.
use std::ops::RangeInclusive;
use std::{any::Any, sync::Arc};
use crate::{
@@ -94,17 +93,19 @@ impl Shape {
}
/// A horizontal line.
pub fn hline(x: RangeInclusive<f32>, y: f32, stroke: impl Into<Stroke>) -> Self {
pub fn hline(x: impl Into<Rangef>, y: f32, stroke: impl Into<Stroke>) -> Self {
let x = x.into();
Shape::LineSegment {
points: [pos2(*x.start(), y), pos2(*x.end(), y)],
points: [pos2(x.min, y), pos2(x.max, y)],
stroke: stroke.into(),
}
}
/// A vertical line.
pub fn vline(x: f32, y: RangeInclusive<f32>, stroke: impl Into<Stroke>) -> Self {
pub fn vline(x: f32, y: impl Into<Rangef>, stroke: impl Into<Stroke>) -> Self {
let y = y.into();
Shape::LineSegment {
points: [pos2(x, *y.start()), pos2(x, *y.end())],
points: [pos2(x, y.min), pos2(x, y.max)],
stroke: stroke.into(),
}
}

View File

@@ -106,7 +106,7 @@ impl AllocInfo {
element_size: ElementSize::Homogeneous(element_size),
num_allocs: 1,
num_elements: slice.len(),
num_bytes: slice.len() * element_size,
num_bytes: std::mem::size_of_val(slice),
}
}
@@ -183,7 +183,7 @@ impl PaintStats {
stats.shape_vec.element_size = ElementSize::Heterogenous; // nicer display later
stats.shapes = AllocInfo::from_slice(shapes);
for ClippedShape(_, shape) in shapes {
for ClippedShape { shape, .. } in shapes {
stats.add(shape);
}
stats

View File

@@ -13,17 +13,15 @@ use emath::*;
#[allow(clippy::approx_constant)]
mod precomputed_vertices {
/*
fn main() {
let n = 64;
println!("pub const CIRCLE_{}: [Vec2; {}] = [", n, n+1);
for i in 0..=n {
let a = std::f64::consts::TAU * i as f64 / n as f64;
println!(" vec2({:.06}, {:.06}),", a.cos(), a.sin());
}
println!("];")
}
*/
// fn main() {
// let n = 64;
// println!("pub const CIRCLE_{}: [Vec2; {}] = [", n, n+1);
// for i in 0..=n {
// let a = std::f64::consts::TAU * i as f64 / n as f64;
// println!(" vec2({:.06}, {:.06}),", a.cos(), a.sin());
// }
// println!("];")
// }
use emath::{vec2, Vec2};
@@ -1036,7 +1034,10 @@ impl Tessellator {
clipped_shape: ClippedShape,
out_primitives: &mut Vec<ClippedPrimitive>,
) {
let ClippedShape(new_clip_rect, new_shape) = clipped_shape;
let ClippedShape {
clip_rect: new_clip_rect,
shape: new_shape,
} = clipped_shape;
if !new_clip_rect.is_positive() {
return; // skip empty clip rectangles
@@ -1044,7 +1045,13 @@ impl Tessellator {
if let Shape::Vec(shapes) = new_shape {
for shape in shapes {
self.tessellate_clipped_shape(ClippedShape(new_clip_rect, shape), out_primitives);
self.tessellate_clipped_shape(
ClippedShape {
clip_rect: new_clip_rect,
shape,
},
out_primitives,
);
}
return;
}
@@ -1641,7 +1648,10 @@ fn test_tessellator() {
shapes.push(Shape::mesh(mesh));
let shape = Shape::Vec(shapes);
let clipped_shapes = vec![ClippedShape(rect, shape)];
let clipped_shapes = vec![ClippedShape {
clip_rect: rect,
shape,
}];
let font_tex_size = [1024, 1024]; // unused
let prepared_discs = vec![]; // unused

View File

@@ -78,11 +78,15 @@ impl Default for GlyphInfo {
pub struct FontImpl {
name: String,
ab_glyph_font: ab_glyph::FontArc,
/// Maximum character height
scale_in_pixels: u32,
height_in_points: f32,
// move each character by this much (hack)
y_offset: f32,
ascent: f32,
pixels_per_point: f32,
glyph_info_cache: RwLock<ahash::HashMap<char, GlyphInfo>>, // TODO(emilk): standard Mutex
@@ -320,8 +324,10 @@ type FontIndex = usize;
/// Wrapper over multiple [`FontImpl`] (e.g. a primary + fallbacks for emojis)
pub struct Font {
fonts: Vec<Arc<FontImpl>>,
/// Lazily calculated.
characters: Option<BTreeSet<char>>,
replacement_glyph: (FontIndex, GlyphInfo),
pixels_per_point: f32,
row_height: f32,
@@ -361,8 +367,7 @@ impl Font {
.or_else(|| slf.glyph_info_no_cache_or_fallback(FALLBACK_REPLACEMENT_CHAR))
.unwrap_or_else(|| {
panic!(
"Failed to find replacement characters {:?} or {:?}",
PRIMARY_REPLACEMENT_CHAR, FALLBACK_REPLACEMENT_CHAR
"Failed to find replacement characters {PRIMARY_REPLACEMENT_CHAR:?} or {FALLBACK_REPLACEMENT_CHAR:?}"
)
});
slf.replacement_glyph = replacement_glyph;

View File

@@ -200,7 +200,7 @@ fn ab_glyph_font_from_font_data(name: &str, data: &FontData) -> ab_glyph::FontAr
.map(ab_glyph::FontArc::from)
}
}
.unwrap_or_else(|err| panic!("Error parsing {:?} TTF/OTF font file: {}", name, err))
.unwrap_or_else(|err| panic!("Error parsing {name:?} TTF/OTF font file: {err}"))
}
/// Describes the font data and the sizes to use.
@@ -586,8 +586,7 @@ impl FontsImpl {
) -> Self {
assert!(
0.0 < pixels_per_point && pixels_per_point < 100.0,
"pixels_per_point out of range: {}",
pixels_per_point
"pixels_per_point out of range: {pixels_per_point}"
);
let texture_width = max_texture_side.at_most(8 * 1024);
@@ -627,9 +626,8 @@ impl FontsImpl {
.entry((HashableF32(*size), family.clone()))
.or_insert_with(|| {
let fonts = &self.definitions.families.get(family);
let fonts = fonts.unwrap_or_else(|| {
panic!("FontFamily::{:?} is not bound to any fonts", family)
});
let fonts = fonts
.unwrap_or_else(|| panic!("FontFamily::{family:?} is not bound to any fonts"));
let fonts: Vec<Arc<FontImpl>> = fonts
.iter()
@@ -752,17 +750,14 @@ impl FontImplCache {
let (tweak, ab_glyph_font) = self
.ab_glyph_fonts
.get(font_name)
.unwrap_or_else(|| panic!("No font data found for {:?}", font_name))
.unwrap_or_else(|| panic!("No font data found for {font_name:?}"))
.clone();
let scale_in_pixels = self.pixels_per_point * scale_in_points;
// Scale the font properly (see https://github.com/emilk/egui/issues/2068).
let units_per_em = ab_glyph_font.units_per_em().unwrap_or_else(|| {
panic!(
"The font unit size of {:?} exceeds the expected range (16..=16384)",
font_name
)
panic!("The font unit size of {font_name:?} exceeds the expected range (16..=16384)")
});
let font_scaling = ab_glyph_font.height_unscaled() / units_per_em;
let scale_in_pixels = scale_in_pixels * font_scaling;

Some files were not shown because too many files have changed in this diff Show More