Merge remote-tracking branch 'egui/master' into dynamic-grid
This commit is contained in:
commit
275fa8c276
42 changed files with 335 additions and 290 deletions
|
@ -16,10 +16,12 @@ NOTE: [`epaint`](epaint/CHANGELOG.md), [`eframe`](eframe/CHANGELOG.md), [`egui_w
|
|||
### Changed 🔧
|
||||
* `ClippedMesh` has been replaced with `ClippedPrimitive` ([#1351](https://github.com/emilk/egui/pull/1351)).
|
||||
* Renamed `Frame::margin` to `Frame::inner_margin`.
|
||||
* Renamed `AlphaImage` to `FontImage` to discourage any other use for it ([#1412](https://github.com/emilk/egui/pull/1412)).
|
||||
|
||||
### Fixed 🐛
|
||||
* Fixed ComboBoxes always being rendered left-aligned ([#1304](https://github.com/emilk/egui/pull/1304)).
|
||||
* Fixed ui code that could lead to a deadlock ([#1380](https://github.com/emilk/egui/pull/1380)).
|
||||
* Text is darker and more readable in bright mode ([#1412](https://github.com/emilk/egui/pull/1412)).
|
||||
|
||||
### Removed 🔥
|
||||
* Removed the `single_threaded/multi_threaded` flags - egui is now always thread-safe ([#1390](https://github.com/emilk/egui/pull/1390)).
|
||||
|
|
|
@ -331,12 +331,6 @@ It is common to use `egui` from a game engine (using e.g. [`bevy_egui`](https://
|
|||
but you can also use `egui` stand-alone using `eframe`. `eframe` has integration for web and native, and handles input and rendering.
|
||||
The _frame_ in `eframe` stands both for the frame in which your egui app resides and also for "framework" (`frame` is a framework, `egui` is a library).
|
||||
|
||||
### Why is `egui_web` using so much CPU in Firefox?
|
||||
On Linux and Mac, Firefox will copy the WebGL render target from GPU, to CPU and then back again: https://bugzilla.mozilla.org/show_bug.cgi?id=1010527#c0
|
||||
|
||||
### Why does my web app not fill the full width of the screen?
|
||||
To alleviate the above mentioned performance issues the default max-width of an egui web app is 1024 points. You can change this by overriding the `fn max_size_points` of [`epi::App`](https://docs.rs/epi/latest/epi/trait.App.html).
|
||||
|
||||
### How do I render 3D stuff in an egui area?
|
||||
There are multiple ways to combine egui with 3D. The simplest way is to use a 3D library and have egui sit on top of the 3D view. See for instance [`bevy_egui`](https://github.com/mvlabat/bevy_egui) or [`three-d`](https://github.com/asny/three-d).
|
||||
|
||||
|
|
|
@ -9,7 +9,16 @@ NOTE: [`egui_web`](../egui_web/CHANGELOG.md), [`egui-winit`](../egui-winit/CHANG
|
|||
* Remove the `egui_glium` feature. `eframe` will now always use `egui_glow` as the native backend ([#1357](https://github.com/emilk/egui/pull/1357)).
|
||||
* Removed `Frame::request_repaint` - just call `egui::Context::request_repaint` for the same effect ([#1366](https://github.com/emilk/egui/pull/1366)).
|
||||
* Use full browser width by default ([#1378](https://github.com/emilk/egui/pull/1378)).
|
||||
* Add new `NativeOptions`: `vsync`, `multisampling`, `depth_buffer`, `stencil_buffer`.
|
||||
* Added new `NativeOptions`: `vsync`, `multisampling`, `depth_buffer`, `stencil_buffer`.
|
||||
* Changed app creation/setup ([#1363](https://github.com/emilk/egui/pull/1363)):
|
||||
* Removed `App::setup` and `App::name`.
|
||||
* Provide `CreationContext` when creating app with egui context, storage, integration info and glow context.
|
||||
* Change interface of `run_native` and `start_web`.
|
||||
* Added `Frame::storage()` and `Frame::storage_mut()` ([#1418](https://github.com/emilk/egui/pull/1418)).
|
||||
* You can now load/save state in `App::update`
|
||||
* Changed `App::update` to take `&mut Frame` instead of `&Frame`.
|
||||
* `Frame` is no longer `Clone` or `Sync`.
|
||||
* Add `glow` (OpenGL) context to `Frame` ([#1425](https://github.com/emilk/egui/pull/1425)).
|
||||
|
||||
|
||||
## 0.17.0 - 2022-02-22
|
||||
|
|
|
@ -23,7 +23,7 @@ impl eframe::App for MyApp {
|
|||
self.can_exit
|
||||
}
|
||||
|
||||
fn update(&mut self, ctx: &egui::Context, frame: &eframe::Frame) {
|
||||
fn update(&mut self, ctx: &egui::Context, frame: &mut eframe::Frame) {
|
||||
egui::CentralPanel::default().show(ctx, |ui| {
|
||||
ui.heading("Try to close the window");
|
||||
});
|
||||
|
|
|
@ -46,7 +46,7 @@ impl MyApp {
|
|||
}
|
||||
|
||||
impl eframe::App for MyApp {
|
||||
fn update(&mut self, ctx: &egui::Context, _frame: &eframe::Frame) {
|
||||
fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) {
|
||||
egui::CentralPanel::default().show(ctx, |ui| {
|
||||
ui.horizontal(|ui| {
|
||||
ui.spacing_mut().item_spacing.x = 0.0;
|
||||
|
|
|
@ -37,7 +37,7 @@ impl MyApp {
|
|||
}
|
||||
|
||||
impl eframe::App for MyApp {
|
||||
fn update(&mut self, ctx: &egui::Context, _frame: &eframe::Frame) {
|
||||
fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) {
|
||||
egui::CentralPanel::default().show(ctx, |ui| {
|
||||
egui::widgets::global_dark_light_mode_buttons(ui);
|
||||
|
||||
|
|
|
@ -54,7 +54,7 @@ impl MyApp {
|
|||
}
|
||||
|
||||
impl eframe::App for MyApp {
|
||||
fn update(&mut self, ctx: &egui::Context, _frame: &eframe::Frame) {
|
||||
fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) {
|
||||
egui::CentralPanel::default().show(ctx, |ui| {
|
||||
ui.heading("egui using custom fonts");
|
||||
ui.text_edit_multiline(&mut self.text);
|
||||
|
|
|
@ -28,7 +28,7 @@ impl eframe::App for MyApp {
|
|||
egui::Rgba::TRANSPARENT // Make sure we don't paint anything behind the rounded corners
|
||||
}
|
||||
|
||||
fn update(&mut self, ctx: &egui::Context, frame: &eframe::Frame) {
|
||||
fn update(&mut self, ctx: &egui::Context, frame: &mut eframe::Frame) {
|
||||
custon_window_frame(ctx, frame, "egui with custom frame", |ui| {
|
||||
ui.label("This is just the contents of the window");
|
||||
ui.horizontal(|ui| {
|
||||
|
@ -41,7 +41,7 @@ impl eframe::App for MyApp {
|
|||
|
||||
fn custon_window_frame(
|
||||
ctx: &egui::Context,
|
||||
frame: &eframe::Frame,
|
||||
frame: &mut eframe::Frame,
|
||||
title: &str,
|
||||
add_contents: impl FnOnce(&mut egui::Ui),
|
||||
) {
|
||||
|
|
|
@ -20,7 +20,7 @@ struct MyApp {
|
|||
}
|
||||
|
||||
impl eframe::App for MyApp {
|
||||
fn update(&mut self, ctx: &egui::Context, _frame: &eframe::Frame) {
|
||||
fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) {
|
||||
let promise = self.promise.get_or_insert_with(|| {
|
||||
// Begin download.
|
||||
// We download the image using `ehttp`, a library that works both in WASM and on native.
|
||||
|
|
|
@ -21,7 +21,7 @@ struct MyApp {
|
|||
}
|
||||
|
||||
impl eframe::App for MyApp {
|
||||
fn update(&mut self, ctx: &egui::Context, _frame: &eframe::Frame) {
|
||||
fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) {
|
||||
egui::CentralPanel::default().show(ctx, |ui| {
|
||||
ui.label("Drag-and-drop files onto the window!");
|
||||
|
||||
|
|
|
@ -26,7 +26,7 @@ impl Default for MyApp {
|
|||
}
|
||||
|
||||
impl eframe::App for MyApp {
|
||||
fn update(&mut self, ctx: &egui::Context, frame: &eframe::Frame) {
|
||||
fn update(&mut self, ctx: &egui::Context, frame: &mut eframe::Frame) {
|
||||
egui::CentralPanel::default().show(ctx, |ui| {
|
||||
ui.heading("My egui Application");
|
||||
ui.horizontal(|ui| {
|
||||
|
|
|
@ -32,7 +32,7 @@ impl Default for MyApp {
|
|||
}
|
||||
|
||||
impl eframe::App for MyApp {
|
||||
fn update(&mut self, ctx: &egui::Context, _frame: &eframe::Frame) {
|
||||
fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) {
|
||||
egui::CentralPanel::default().show(ctx, |ui| {
|
||||
ui.heading("This is an image:");
|
||||
self.image.show(ui);
|
||||
|
|
|
@ -35,7 +35,7 @@ impl Default for MyApp {
|
|||
}
|
||||
|
||||
impl eframe::App for MyApp {
|
||||
fn update(&mut self, ctx: &egui::Context, _frame: &eframe::Frame) {
|
||||
fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) {
|
||||
egui::CentralPanel::default().show(ctx, |ui| {
|
||||
ui.heading("SVG example");
|
||||
ui.label("The SVG is rasterized and displayed as a texture.");
|
||||
|
|
|
@ -37,7 +37,7 @@
|
|||
//! }
|
||||
//!
|
||||
//! impl eframe::App for MyEguiApp {
|
||||
//! fn update(&mut self, ctx: &egui::Context, frame: &eframe::Frame) {
|
||||
//! fn update(&mut self, ctx: &egui::Context, frame: &mut eframe::Frame) {
|
||||
//! egui::CentralPanel::default().show(ctx, |ui| {
|
||||
//! ui.heading("Hello World!");
|
||||
//! });
|
||||
|
@ -126,7 +126,7 @@ pub fn start_web(canvas_id: &str, app_creator: AppCreator) -> Result<(), wasm_bi
|
|||
/// }
|
||||
///
|
||||
/// impl eframe::App for MyEguiApp {
|
||||
/// fn update(&mut self, ctx: &egui::Context, frame: &eframe::Frame) {
|
||||
/// fn update(&mut self, ctx: &egui::Context, frame: &mut eframe::Frame) {
|
||||
/// egui::CentralPanel::default().show(ctx, |ui| {
|
||||
/// ui.heading("Hello World!");
|
||||
/// });
|
||||
|
|
|
@ -3,6 +3,7 @@ All notable changes to the `egui-winit` integration will be noted in this file.
|
|||
|
||||
|
||||
## Unreleased
|
||||
* Reexport `egui` crate
|
||||
|
||||
|
||||
## 0.17.0 - 2022-02-22
|
||||
|
|
|
@ -124,20 +124,7 @@ pub fn handle_app_output(
|
|||
// ----------------------------------------------------------------------------
|
||||
|
||||
/// For loading/saving app state and/or egui memory to disk.
|
||||
pub struct Persistence {
|
||||
storage: Option<Box<dyn epi::Storage>>,
|
||||
last_auto_save: instant::Instant,
|
||||
}
|
||||
|
||||
#[allow(clippy::unused_self)]
|
||||
impl Persistence {
|
||||
#[cfg(feature = "persistence")]
|
||||
const EGUI_MEMORY_KEY: &'static str = "egui";
|
||||
#[cfg(feature = "persistence")]
|
||||
const WINDOW_KEY: &'static str = "window";
|
||||
|
||||
pub fn from_app_name(app_name: &str) -> Self {
|
||||
fn create_storage(_app_name: &str) -> Option<Box<dyn epi::Storage>> {
|
||||
pub fn create_storage(_app_name: &str) -> Option<Box<dyn epi::Storage>> {
|
||||
#[cfg(feature = "persistence")]
|
||||
if let Some(storage) = epi::file_storage::FileStorage::from_app_name(_app_name) {
|
||||
return Some(Box::new(storage));
|
||||
|
@ -145,83 +132,12 @@ impl Persistence {
|
|||
None
|
||||
}
|
||||
|
||||
Self {
|
||||
storage: create_storage(app_name),
|
||||
last_auto_save: instant::Instant::now(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn storage(&self) -> Option<&dyn epi::Storage> {
|
||||
self.storage.as_deref()
|
||||
}
|
||||
|
||||
#[cfg(feature = "persistence")]
|
||||
pub fn load_window_settings(&self) -> Option<crate::WindowSettings> {
|
||||
epi::get_value(&**self.storage.as_ref()?, Self::WINDOW_KEY)
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "persistence"))]
|
||||
pub fn load_window_settings(&self) -> Option<crate::WindowSettings> {
|
||||
None
|
||||
}
|
||||
|
||||
#[cfg(feature = "persistence")]
|
||||
pub fn load_memory(&self) -> Option<egui::Memory> {
|
||||
epi::get_value(&**self.storage.as_ref()?, Self::EGUI_MEMORY_KEY)
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "persistence"))]
|
||||
pub fn load_memory(&self) -> Option<egui::Memory> {
|
||||
None
|
||||
}
|
||||
|
||||
pub fn save(
|
||||
&mut self,
|
||||
_app: &mut dyn epi::App,
|
||||
_egui_ctx: &egui::Context,
|
||||
_window: &winit::window::Window,
|
||||
) {
|
||||
#[cfg(feature = "persistence")]
|
||||
if let Some(storage) = &mut self.storage {
|
||||
if _app.persist_native_window() {
|
||||
epi::set_value(
|
||||
storage.as_mut(),
|
||||
Self::WINDOW_KEY,
|
||||
&crate::WindowSettings::from_display(_window),
|
||||
);
|
||||
}
|
||||
if _app.persist_egui_memory() {
|
||||
epi::set_value(
|
||||
storage.as_mut(),
|
||||
Self::EGUI_MEMORY_KEY,
|
||||
&*_egui_ctx.memory(),
|
||||
);
|
||||
}
|
||||
_app.save(storage.as_mut());
|
||||
storage.flush();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn maybe_autosave(
|
||||
&mut self,
|
||||
app: &mut dyn epi::App,
|
||||
egui_ctx: &egui::Context,
|
||||
window: &winit::window::Window,
|
||||
) {
|
||||
let now = instant::Instant::now();
|
||||
if now - self.last_auto_save > app.auto_save_interval() {
|
||||
self.save(app, egui_ctx, window);
|
||||
self.last_auto_save = now;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
/// Everything needed to make a winit-based integration for [`epi`].
|
||||
pub struct EpiIntegration {
|
||||
pub frame: epi::Frame,
|
||||
pub persistence: crate::epi::Persistence,
|
||||
last_auto_save: instant::Instant,
|
||||
pub egui_ctx: egui::Context,
|
||||
pending_full_output: egui::FullOutput,
|
||||
egui_winit: crate::State,
|
||||
|
@ -233,17 +149,18 @@ pub struct EpiIntegration {
|
|||
impl EpiIntegration {
|
||||
pub fn new(
|
||||
integration_name: &'static str,
|
||||
gl: std::rc::Rc<glow::Context>,
|
||||
max_texture_side: usize,
|
||||
window: &winit::window::Window,
|
||||
persistence: crate::epi::Persistence,
|
||||
storage: Option<Box<dyn epi::Storage>>,
|
||||
) -> Self {
|
||||
let egui_ctx = egui::Context::default();
|
||||
|
||||
*egui_ctx.memory() = persistence.load_memory().unwrap_or_default();
|
||||
*egui_ctx.memory() = load_egui_memory(storage.as_deref()).unwrap_or_default();
|
||||
|
||||
let prefer_dark_mode = prefer_dark_mode();
|
||||
|
||||
let frame = epi::Frame::new(epi::backend::FrameData {
|
||||
let frame = epi::Frame {
|
||||
info: epi::IntegrationInfo {
|
||||
name: integration_name,
|
||||
web_info: None,
|
||||
|
@ -252,7 +169,9 @@ impl EpiIntegration {
|
|||
native_pixels_per_point: Some(crate::native_pixels_per_point(window)),
|
||||
},
|
||||
output: Default::default(),
|
||||
});
|
||||
storage,
|
||||
gl,
|
||||
};
|
||||
|
||||
if prefer_dark_mode == Some(true) {
|
||||
egui_ctx.set_visuals(egui::Visuals::dark());
|
||||
|
@ -262,7 +181,7 @@ impl EpiIntegration {
|
|||
|
||||
Self {
|
||||
frame,
|
||||
persistence,
|
||||
last_auto_save: instant::Instant::now(),
|
||||
egui_ctx,
|
||||
egui_winit: crate::State::new(max_texture_side, window),
|
||||
pending_full_output: Default::default(),
|
||||
|
@ -311,7 +230,7 @@ impl EpiIntegration {
|
|||
|
||||
let raw_input = self.egui_winit.take_egui_input(window);
|
||||
let full_output = self.egui_ctx.run(raw_input, |egui_ctx| {
|
||||
app.update(egui_ctx, &self.frame);
|
||||
app.update(egui_ctx, &mut self.frame);
|
||||
});
|
||||
self.pending_full_output.append(full_output);
|
||||
let full_output = std::mem::take(&mut self.pending_full_output);
|
||||
|
@ -327,7 +246,7 @@ impl EpiIntegration {
|
|||
}
|
||||
|
||||
let frame_time = (instant::Instant::now() - frame_start).as_secs_f64() as f32;
|
||||
self.frame.lock().info.cpu_usage = Some(frame_time);
|
||||
self.frame.info.cpu_usage = Some(frame_time);
|
||||
|
||||
full_output
|
||||
}
|
||||
|
@ -341,12 +260,59 @@ impl EpiIntegration {
|
|||
.handle_platform_output(window, &self.egui_ctx, platform_output);
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
// Persistance stuff:
|
||||
|
||||
pub fn maybe_autosave(&mut self, app: &mut dyn epi::App, window: &winit::window::Window) {
|
||||
self.persistence
|
||||
.maybe_autosave(&mut *app, &self.egui_ctx, window);
|
||||
let now = instant::Instant::now();
|
||||
if now - self.last_auto_save > app.auto_save_interval() {
|
||||
self.save(app, window);
|
||||
self.last_auto_save = now;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn save(&mut self, _app: &mut dyn epi::App, _window: &winit::window::Window) {
|
||||
#[cfg(feature = "persistence")]
|
||||
if let Some(storage) = self.frame.storage_mut() {
|
||||
if _app.persist_native_window() {
|
||||
epi::set_value(
|
||||
storage,
|
||||
STORAGE_WINDOW_KEY,
|
||||
&crate::WindowSettings::from_display(_window),
|
||||
);
|
||||
}
|
||||
if _app.persist_egui_memory() {
|
||||
epi::set_value(storage, STORAGE_EGUI_MEMORY_KEY, &*self.egui_ctx.memory());
|
||||
}
|
||||
_app.save(storage);
|
||||
storage.flush();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "persistence")]
|
||||
const STORAGE_EGUI_MEMORY_KEY: &str = "egui";
|
||||
#[cfg(feature = "persistence")]
|
||||
const STORAGE_WINDOW_KEY: &str = "window";
|
||||
|
||||
pub fn load_window_settings(_storage: Option<&dyn epi::Storage>) -> Option<crate::WindowSettings> {
|
||||
#[cfg(feature = "persistence")]
|
||||
{
|
||||
epi::get_value(_storage?, STORAGE_WINDOW_KEY)
|
||||
}
|
||||
#[cfg(not(feature = "persistence"))]
|
||||
None
|
||||
}
|
||||
|
||||
pub fn load_egui_memory(_storage: Option<&dyn epi::Storage>) -> Option<egui::Memory> {
|
||||
#[cfg(feature = "persistence")]
|
||||
{
|
||||
epi::get_value(_storage?, STORAGE_EGUI_MEMORY_KEY)
|
||||
}
|
||||
#[cfg(not(feature = "persistence"))]
|
||||
None
|
||||
}
|
||||
|
||||
#[cfg(feature = "dark-light")]
|
||||
fn prefer_dark_mode() -> Option<bool> {
|
||||
match dark_light::detect() {
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
|
||||
#![allow(clippy::manual_range_contains)]
|
||||
|
||||
pub use egui;
|
||||
pub use winit;
|
||||
|
||||
pub mod clipboard;
|
||||
|
|
|
@ -1,6 +1,9 @@
|
|||
use crate::{style::WidgetVisuals, *};
|
||||
use epaint::Shape;
|
||||
|
||||
/// A function that paints the `ComboBox` icon
|
||||
pub type IconPainter = Box<dyn FnOnce(&Ui, Rect, &WidgetVisuals, bool)>;
|
||||
|
||||
/// A drop-down selection menu with a descriptive label.
|
||||
///
|
||||
/// ```
|
||||
|
@ -24,6 +27,7 @@ pub struct ComboBox {
|
|||
label: Option<WidgetText>,
|
||||
selected_text: WidgetText,
|
||||
width: Option<f32>,
|
||||
icon: Option<IconPainter>,
|
||||
}
|
||||
|
||||
impl ComboBox {
|
||||
|
@ -34,6 +38,7 @@ impl ComboBox {
|
|||
label: Some(label.into()),
|
||||
selected_text: Default::default(),
|
||||
width: None,
|
||||
icon: None,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -45,6 +50,7 @@ impl ComboBox {
|
|||
label: Some(label),
|
||||
selected_text: Default::default(),
|
||||
width: None,
|
||||
icon: None,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -55,6 +61,7 @@ impl ComboBox {
|
|||
label: Default::default(),
|
||||
selected_text: Default::default(),
|
||||
width: None,
|
||||
icon: None,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -70,6 +77,41 @@ impl ComboBox {
|
|||
self
|
||||
}
|
||||
|
||||
/// Use the provided function to render a different `ComboBox` icon.
|
||||
/// Defaults to a triangle that expands when the cursor is hovering over the `ComboBox`.
|
||||
///
|
||||
/// For example:
|
||||
/// ```
|
||||
/// # egui::__run_test_ui(|ui| {
|
||||
/// # let text = "Selected text";
|
||||
/// pub fn filled_triangle(
|
||||
/// ui: &egui::Ui,
|
||||
/// rect: egui::Rect,
|
||||
/// visuals: &egui::style::WidgetVisuals,
|
||||
/// _is_open: bool,
|
||||
/// ) {
|
||||
/// let rect = egui::Rect::from_center_size(
|
||||
/// rect.center(),
|
||||
/// egui::Vec2::new(rect.width() * 0.6, rect.height() * 0.4),
|
||||
/// );
|
||||
/// ui.painter().add(egui::Shape::convex_polygon(
|
||||
/// vec![rect.left_top(), rect.right_top(), rect.center_bottom()],
|
||||
/// visuals.fg_stroke.color,
|
||||
/// visuals.fg_stroke,
|
||||
/// ));
|
||||
/// }
|
||||
///
|
||||
/// egui::ComboBox::from_id_source("my-combobox")
|
||||
/// .selected_text(text)
|
||||
/// .icon(filled_triangle)
|
||||
/// .show_ui(ui, |_ui| {});
|
||||
/// # });
|
||||
/// ```
|
||||
pub fn icon(mut self, icon_fn: impl FnOnce(&Ui, Rect, &WidgetVisuals, bool) + 'static) -> Self {
|
||||
self.icon = Some(Box::new(icon_fn));
|
||||
self
|
||||
}
|
||||
|
||||
/// Show the combo box, with the given ui code for the menu contents.
|
||||
///
|
||||
/// Returns `InnerResponse { inner: None }` if the combo box is closed.
|
||||
|
@ -91,6 +133,7 @@ impl ComboBox {
|
|||
label,
|
||||
selected_text,
|
||||
width,
|
||||
icon,
|
||||
} = self;
|
||||
|
||||
let button_id = ui.make_persistent_id(id_source);
|
||||
|
@ -99,7 +142,7 @@ impl ComboBox {
|
|||
if let Some(width) = width {
|
||||
ui.spacing_mut().slider_width = width; // yes, this is ugly. Will remove later.
|
||||
}
|
||||
let mut ir = combo_box_dyn(ui, button_id, selected_text, menu_contents);
|
||||
let mut ir = combo_box_dyn(ui, button_id, selected_text, menu_contents, icon);
|
||||
if let Some(label) = label {
|
||||
ir.response
|
||||
.widget_info(|| WidgetInfo::labeled(WidgetType::ComboBox, label.text()));
|
||||
|
@ -165,6 +208,7 @@ fn combo_box_dyn<'c, R>(
|
|||
button_id: Id,
|
||||
selected_text: WidgetText,
|
||||
menu_contents: Box<dyn FnOnce(&mut Ui) -> R + 'c>,
|
||||
icon: Option<IconPainter>,
|
||||
) -> InnerResponse<Option<R>> {
|
||||
let popup_id = button_id.with("popup");
|
||||
|
||||
|
@ -192,7 +236,17 @@ fn combo_box_dyn<'c, R>(
|
|||
} else {
|
||||
ui.style().interact(&response)
|
||||
};
|
||||
paint_icon(ui.painter(), icon_rect.expand(visuals.expansion), visuals);
|
||||
|
||||
if let Some(icon) = icon {
|
||||
icon(
|
||||
ui,
|
||||
icon_rect.expand(visuals.expansion),
|
||||
visuals,
|
||||
is_popup_open,
|
||||
);
|
||||
} else {
|
||||
paint_default_icon(ui.painter(), icon_rect.expand(visuals.expansion), visuals);
|
||||
}
|
||||
|
||||
let text_rect = Align2::LEFT_CENTER.align_size_within_rect(galley.size(), rect);
|
||||
galley.paint_with_visuals(ui.painter(), text_rect.min, visuals);
|
||||
|
@ -262,7 +316,7 @@ fn button_frame(
|
|||
response
|
||||
}
|
||||
|
||||
fn paint_icon(painter: &Painter, rect: Rect, visuals: &WidgetVisuals) {
|
||||
fn paint_default_icon(painter: &Painter, rect: Rect, visuals: &WidgetVisuals) {
|
||||
let rect = Rect::from_center_size(
|
||||
rect.center(),
|
||||
vec2(rect.width() * 0.7, rect.height() * 0.45),
|
||||
|
|
|
@ -17,7 +17,7 @@ impl Default for WrappedTextureManager {
|
|||
// Will be filled in later
|
||||
let font_id = tex_mngr.alloc(
|
||||
"egui_font_texture".into(),
|
||||
epaint::AlphaImage::new([0, 0]).into(),
|
||||
epaint::FontImage::new([0, 0]).into(),
|
||||
);
|
||||
assert_eq!(font_id, TextureId::default());
|
||||
|
||||
|
|
|
@ -307,7 +307,7 @@ pub use epaint::{
|
|||
color, mutex,
|
||||
text::{FontData, FontDefinitions, FontFamily, FontId, FontTweak},
|
||||
textures::TexturesDelta,
|
||||
AlphaImage, ClippedPrimitive, Color32, ColorImage, ImageData, Mesh, PaintCallback,
|
||||
ClippedPrimitive, Color32, ColorImage, FontImage, ImageData, Mesh, PaintCallback,
|
||||
PaintCallbackInfo, Rgba, Rounding, Shape, Stroke, TextureHandle, TextureId,
|
||||
};
|
||||
|
||||
|
|
|
@ -783,7 +783,17 @@ impl Ui {
|
|||
&self.placer
|
||||
}
|
||||
|
||||
pub(crate) fn cursor(&self) -> Rect {
|
||||
/// Where the next widget will be put.
|
||||
///
|
||||
/// One side of this will always be infinite: the direction in which new widgets will be added.
|
||||
/// The opposing side is what is incremented.
|
||||
/// The crossing sides are initialized to `max_rect`.
|
||||
///
|
||||
/// So one can think of `cursor` as a constraint on the available region.
|
||||
///
|
||||
/// If something has already been added, this will point to `style.spacing.item_spacing` beyond the latest child.
|
||||
/// The cursor can thus be `style.spacing.item_spacing` pixels outside of the `min_rect`.
|
||||
pub fn cursor(&self) -> Rect {
|
||||
self.placer.cursor()
|
||||
}
|
||||
|
||||
|
@ -792,7 +802,7 @@ impl Ui {
|
|||
}
|
||||
|
||||
/// Where do we expect a zero-sized widget to be placed?
|
||||
pub(crate) fn next_widget_position(&self) -> Pos2 {
|
||||
pub fn next_widget_position(&self) -> Pos2 {
|
||||
self.placer.next_widget_position()
|
||||
}
|
||||
|
||||
|
|
|
@ -30,7 +30,7 @@ impl Default for ColorTest {
|
|||
}
|
||||
|
||||
impl epi::App for ColorTest {
|
||||
fn update(&mut self, ctx: &egui::Context, frame: &epi::Frame) {
|
||||
fn update(&mut self, ctx: &egui::Context, frame: &mut epi::Frame) {
|
||||
egui::CentralPanel::default().show(ctx, |ui| {
|
||||
if frame.is_web() {
|
||||
ui.label(
|
||||
|
|
|
@ -6,7 +6,7 @@ pub struct DemoApp {
|
|||
}
|
||||
|
||||
impl epi::App for DemoApp {
|
||||
fn update(&mut self, ctx: &egui::Context, _frame: &epi::Frame) {
|
||||
fn update(&mut self, ctx: &egui::Context, _frame: &mut epi::Frame) {
|
||||
self.demo_windows.ui(ctx);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -33,7 +33,7 @@ impl Default for FractalClock {
|
|||
}
|
||||
|
||||
impl epi::App for FractalClock {
|
||||
fn update(&mut self, ctx: &egui::Context, _frame: &epi::Frame) {
|
||||
fn update(&mut self, ctx: &egui::Context, _frame: &mut epi::Frame) {
|
||||
egui::CentralPanel::default()
|
||||
.frame(Frame::dark_canvas(&ctx.style()))
|
||||
.show(ctx, |ui| self.ui(ui, crate::seconds_since_midnight()));
|
||||
|
|
|
@ -54,7 +54,7 @@ impl Default for HttpApp {
|
|||
}
|
||||
|
||||
impl epi::App for HttpApp {
|
||||
fn update(&mut self, ctx: &egui::Context, frame: &epi::Frame) {
|
||||
fn update(&mut self, ctx: &egui::Context, frame: &mut epi::Frame) {
|
||||
egui::TopBottomPanel::bottom("http_bottom").show(ctx, |ui| {
|
||||
let layout = egui::Layout::top_down(egui::Align::Center).with_main_justify(true);
|
||||
ui.allocate_ui_with_layout(ui.available_size(), layout, |ui| {
|
||||
|
@ -108,7 +108,7 @@ impl epi::App for HttpApp {
|
|||
}
|
||||
}
|
||||
|
||||
fn ui_url(ui: &mut egui::Ui, frame: &epi::Frame, url: &mut String) -> bool {
|
||||
fn ui_url(ui: &mut egui::Ui, frame: &mut epi::Frame, url: &mut String) -> bool {
|
||||
let mut trigger_fetch = false;
|
||||
|
||||
ui.horizontal(|ui| {
|
||||
|
|
|
@ -12,7 +12,7 @@ enum RunMode {
|
|||
/// For instance, a GUI for a thermostat need to repaint each time the temperature changes.
|
||||
/// To ensure the UI is up to date you need to call `egui::Context::request_repaint()` each
|
||||
/// time such an event happens. You can also chose to call `request_repaint()` once every second
|
||||
/// or after every single frame - this is called `Continuous` mode,
|
||||
/// or after every single frame - this is called "Continuous" mode,
|
||||
/// and for games and interactive tools that need repainting every frame anyway, this should be the default.
|
||||
Reactive,
|
||||
|
||||
|
@ -73,7 +73,7 @@ impl Default for BackendPanel {
|
|||
}
|
||||
|
||||
impl BackendPanel {
|
||||
pub fn update(&mut self, ctx: &egui::Context, frame: &epi::Frame) {
|
||||
pub fn update(&mut self, ctx: &egui::Context, frame: &mut epi::Frame) {
|
||||
self.frame_history
|
||||
.on_new_frame(ctx.input().time, frame.info().cpu_usage);
|
||||
|
||||
|
@ -87,7 +87,7 @@ impl BackendPanel {
|
|||
self.egui_windows.windows(ctx);
|
||||
}
|
||||
|
||||
pub fn ui(&mut self, ui: &mut egui::Ui, frame: &epi::Frame) {
|
||||
pub fn ui(&mut self, ui: &mut egui::Ui, frame: &mut epi::Frame) {
|
||||
egui::trace!(ui);
|
||||
ui.vertical_centered(|ui| {
|
||||
ui.heading("💻 Backend");
|
||||
|
@ -142,7 +142,7 @@ impl BackendPanel {
|
|||
}
|
||||
}
|
||||
|
||||
fn integration_ui(&mut self, ui: &mut egui::Ui, frame: &epi::Frame) {
|
||||
fn integration_ui(&mut self, ui: &mut egui::Ui, frame: &mut epi::Frame) {
|
||||
if frame.is_web() {
|
||||
ui.label("egui is an immediate mode GUI written in Rust, compiled to WebAssembly, rendered with WebGL.");
|
||||
ui.label(
|
||||
|
|
|
@ -30,7 +30,7 @@ impl Default for EasyMarkEditor {
|
|||
}
|
||||
|
||||
impl epi::App for EasyMarkEditor {
|
||||
fn update(&mut self, ctx: &egui::Context, _frame: &epi::Frame) {
|
||||
fn update(&mut self, ctx: &egui::Context, _frame: &mut epi::Frame) {
|
||||
egui::TopBottomPanel::bottom("easy_mark_bottom").show(ctx, |ui| {
|
||||
let layout = egui::Layout::top_down(egui::Align::Center).with_main_justify(true);
|
||||
ui.allocate_ui_with_layout(ui.available_size(), layout, |ui| {
|
||||
|
|
|
@ -69,7 +69,7 @@ impl epi::App for WrapApp {
|
|||
egui::Rgba::TRANSPARENT // we set a `CentralPanel` fill color in `demo_windows.rs`
|
||||
}
|
||||
|
||||
fn update(&mut self, ctx: &egui::Context, frame: &epi::Frame) {
|
||||
fn update(&mut self, ctx: &egui::Context, frame: &mut epi::Frame) {
|
||||
if let Some(web_info) = frame.info().web_info.as_ref() {
|
||||
if let Some(anchor) = web_info.location.hash.strip_prefix('#') {
|
||||
self.selected_anchor = anchor.to_owned();
|
||||
|
@ -130,7 +130,7 @@ impl epi::App for WrapApp {
|
|||
}
|
||||
|
||||
impl WrapApp {
|
||||
fn bar_contents(&mut self, ui: &mut egui::Ui, frame: &epi::Frame) {
|
||||
fn bar_contents(&mut self, ui: &mut egui::Ui, frame: &mut epi::Frame) {
|
||||
// A menu-bar is a horizontal layout with some special styles applied.
|
||||
// egui::menu::bar(ui, |ui| {
|
||||
ui.horizontal_wrapped(|ui| {
|
||||
|
|
|
@ -235,7 +235,7 @@ impl Painter {
|
|||
);
|
||||
image.pixels.iter().map(|color| color.to_tuple()).collect()
|
||||
}
|
||||
egui::ImageData::Alpha(image) => {
|
||||
egui::ImageData::Font(image) => {
|
||||
let gamma = 1.0;
|
||||
image
|
||||
.srgba_pixels(gamma)
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
use crate::*;
|
||||
use egui_winit::winit;
|
||||
|
||||
struct RequestRepaintEvent;
|
||||
|
@ -37,8 +36,8 @@ pub use epi::NativeOptions;
|
|||
/// Run an egui app
|
||||
#[allow(unsafe_code)]
|
||||
pub fn run(app_name: &str, native_options: &epi::NativeOptions, app_creator: epi::AppCreator) -> ! {
|
||||
let persistence = egui_winit::epi::Persistence::from_app_name(app_name);
|
||||
let window_settings = persistence.load_window_settings();
|
||||
let storage = egui_winit::epi::create_storage(app_name);
|
||||
let window_settings = egui_winit::epi::load_window_settings(storage.as_deref());
|
||||
let window_builder =
|
||||
egui_winit::epi::window_builder(native_options, &window_settings).with_title(app_name);
|
||||
let event_loop = winit::event_loop::EventLoop::with_user_event();
|
||||
|
@ -50,9 +49,10 @@ pub fn run(app_name: &str, native_options: &epi::NativeOptions, app_creator: epi
|
|||
|
||||
let mut integration = egui_winit::epi::EpiIntegration::new(
|
||||
"egui_glow",
|
||||
gl.clone(),
|
||||
painter.max_texture_side(),
|
||||
gl_window.window(),
|
||||
persistence,
|
||||
storage,
|
||||
);
|
||||
|
||||
{
|
||||
|
@ -65,7 +65,7 @@ pub fn run(app_name: &str, native_options: &epi::NativeOptions, app_creator: epi
|
|||
let mut app = app_creator(&epi::CreationContext {
|
||||
egui_ctx: integration.egui_ctx.clone(),
|
||||
integration_info: integration.frame.info(),
|
||||
storage: integration.persistence.storage(),
|
||||
storage: integration.frame.storage(),
|
||||
gl: gl.clone(),
|
||||
});
|
||||
|
||||
|
@ -86,6 +86,10 @@ pub fn run(app_name: &str, native_options: &epi::NativeOptions, app_creator: epi
|
|||
std::thread::sleep(std::time::Duration::from_millis(10));
|
||||
}
|
||||
|
||||
let screen_size_in_pixels: [u32; 2] = gl_window.window().inner_size().into();
|
||||
|
||||
crate::painter::clear(&gl, screen_size_in_pixels, app.clear_color());
|
||||
|
||||
let egui::FullOutput {
|
||||
platform_output,
|
||||
needs_repaint,
|
||||
|
@ -97,24 +101,14 @@ pub fn run(app_name: &str, native_options: &epi::NativeOptions, app_creator: epi
|
|||
|
||||
let clipped_primitives = integration.egui_ctx.tessellate(shapes);
|
||||
|
||||
// paint:
|
||||
{
|
||||
let color = app.clear_color();
|
||||
unsafe {
|
||||
use glow::HasContext as _;
|
||||
gl.disable(glow::SCISSOR_TEST);
|
||||
gl.clear_color(color[0], color[1], color[2], color[3]);
|
||||
gl.clear(glow::COLOR_BUFFER_BIT);
|
||||
}
|
||||
painter.paint_and_update_textures(
|
||||
gl_window.window().inner_size().into(),
|
||||
screen_size_in_pixels,
|
||||
integration.egui_ctx.pixels_per_point(),
|
||||
&clipped_primitives,
|
||||
&textures_delta,
|
||||
);
|
||||
|
||||
gl_window.swap_buffers().unwrap();
|
||||
}
|
||||
|
||||
{
|
||||
*control_flow = if integration.should_quit() {
|
||||
|
@ -154,9 +148,7 @@ pub fn run(app_name: &str, native_options: &epi::NativeOptions, app_creator: epi
|
|||
gl_window.window().request_redraw(); // TODO: ask egui if the events warrants a repaint instead
|
||||
}
|
||||
winit::event::Event::LoopDestroyed => {
|
||||
integration
|
||||
.persistence
|
||||
.save(&mut *app, &integration.egui_ctx, gl_window.window());
|
||||
integration.save(&mut *app, gl_window.window());
|
||||
app.on_exit(&gl);
|
||||
painter.destroy();
|
||||
}
|
||||
|
|
|
@ -478,7 +478,7 @@ impl Painter {
|
|||
|
||||
self.upload_texture_srgb(delta.pos, image.size, data);
|
||||
}
|
||||
egui::ImageData::Alpha(image) => {
|
||||
egui::ImageData::Font(image) => {
|
||||
assert_eq!(
|
||||
image.width() * image.height(),
|
||||
image.pixels.len(),
|
||||
|
@ -627,11 +627,16 @@ impl Painter {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn clear(gl: &glow::Context, dimension: [u32; 2], clear_color: egui::Rgba) {
|
||||
pub fn clear(gl: &glow::Context, screen_size_in_pixels: [u32; 2], clear_color: egui::Rgba) {
|
||||
unsafe {
|
||||
gl.disable(glow::SCISSOR_TEST);
|
||||
|
||||
gl.viewport(0, 0, dimension[0] as i32, dimension[1] as i32);
|
||||
gl.viewport(
|
||||
0,
|
||||
0,
|
||||
screen_size_in_pixels[0] as i32,
|
||||
screen_size_in_pixels[1] as i32,
|
||||
);
|
||||
|
||||
let clear_color: Color32 = clear_color.into();
|
||||
gl.clear_color(
|
||||
|
|
|
@ -130,7 +130,6 @@ pub struct AppRunner {
|
|||
pub(crate) input: WebInput,
|
||||
app: Box<dyn epi::App>,
|
||||
pub(crate) needs_repaint: std::sync::Arc<NeedRepaint>,
|
||||
storage: LocalStorage,
|
||||
last_save_time: f64,
|
||||
screen_reader: crate::screen_reader::ScreenReader,
|
||||
pub(crate) text_cursor_pos: Option<egui::Pos2>,
|
||||
|
@ -144,7 +143,7 @@ impl AppRunner {
|
|||
|
||||
let prefer_dark_mode = crate::prefer_dark_mode();
|
||||
|
||||
let frame = epi::Frame::new(epi::backend::FrameData {
|
||||
let frame = epi::Frame {
|
||||
info: epi::IntegrationInfo {
|
||||
name: "egui_web",
|
||||
web_info: Some(epi::WebInfo {
|
||||
|
@ -155,7 +154,9 @@ impl AppRunner {
|
|||
native_pixels_per_point: Some(native_pixels_per_point()),
|
||||
},
|
||||
output: Default::default(),
|
||||
});
|
||||
storage: Some(Box::new(LocalStorage::default())),
|
||||
gl: painter.gl().clone(),
|
||||
};
|
||||
|
||||
let needs_repaint: std::sync::Arc<NeedRepaint> = Default::default();
|
||||
|
||||
|
@ -175,12 +176,10 @@ impl AppRunner {
|
|||
egui_ctx.set_visuals(egui::Visuals::light());
|
||||
}
|
||||
|
||||
let storage = LocalStorage::default();
|
||||
|
||||
let app = app_creator(&epi::CreationContext {
|
||||
egui_ctx: egui_ctx.clone(),
|
||||
integration_info: frame.info(),
|
||||
storage: Some(&storage),
|
||||
storage: frame.storage(),
|
||||
gl: painter.painter.gl().clone(),
|
||||
});
|
||||
|
||||
|
@ -191,7 +190,6 @@ impl AppRunner {
|
|||
input: Default::default(),
|
||||
app,
|
||||
needs_repaint,
|
||||
storage,
|
||||
last_save_time: now_sec(),
|
||||
screen_reader: Default::default(),
|
||||
text_cursor_pos: None,
|
||||
|
@ -216,7 +214,9 @@ impl AppRunner {
|
|||
if self.app.persist_egui_memory() {
|
||||
save_memory(&self.egui_ctx);
|
||||
}
|
||||
self.app.save(&mut self.storage);
|
||||
if let Some(storage) = self.frame.storage_mut() {
|
||||
self.app.save(storage);
|
||||
}
|
||||
self.last_save_time = now;
|
||||
}
|
||||
}
|
||||
|
@ -247,7 +247,7 @@ impl AppRunner {
|
|||
let raw_input = self.input.new_frame(canvas_size);
|
||||
|
||||
let full_output = self.egui_ctx.run(raw_input, |egui_ctx| {
|
||||
self.app.update(egui_ctx, &self.frame);
|
||||
self.app.update(egui_ctx, &mut self.frame);
|
||||
});
|
||||
let egui::FullOutput {
|
||||
platform_output,
|
||||
|
@ -271,16 +271,18 @@ impl AppRunner {
|
|||
} = app_output;
|
||||
}
|
||||
|
||||
self.frame.lock().info.cpu_usage = Some((now_sec() - frame_start) as f32);
|
||||
self.frame.info.cpu_usage = Some((now_sec() - frame_start) as f32);
|
||||
Ok((needs_repaint, clipped_primitives))
|
||||
}
|
||||
|
||||
pub fn clear_color_buffer(&self) {
|
||||
self.painter.clear(self.app.clear_color());
|
||||
}
|
||||
|
||||
/// Paint the results of the last call to [`Self::logic`].
|
||||
pub fn paint(&mut self, clipped_primitives: &[egui::ClippedPrimitive]) -> Result<(), JsValue> {
|
||||
let textures_delta = std::mem::take(&mut self.textures_delta);
|
||||
|
||||
self.painter.clear(self.app.clear_color());
|
||||
|
||||
self.painter.paint_and_update_textures(
|
||||
clipped_primitives,
|
||||
self.egui_ctx.pixels_per_point(),
|
||||
|
|
|
@ -32,6 +32,10 @@ impl WrappedGlowPainter {
|
|||
}
|
||||
|
||||
impl WrappedGlowPainter {
|
||||
pub fn gl(&self) -> &std::rc::Rc<glow::Context> {
|
||||
self.painter.gl()
|
||||
}
|
||||
|
||||
pub fn max_texture_side(&self) -> usize {
|
||||
self.painter.max_texture_side()
|
||||
}
|
||||
|
@ -48,7 +52,7 @@ impl WrappedGlowPainter {
|
|||
self.painter.free_texture(tex_id);
|
||||
}
|
||||
|
||||
pub fn clear(&mut self, clear_color: Rgba) {
|
||||
pub fn clear(&self, clear_color: Rgba) {
|
||||
let canvas_dimension = [self.canvas.width(), self.canvas.height()];
|
||||
egui_glow::painter::clear(self.painter.gl(), canvas_dimension, clear_color);
|
||||
}
|
||||
|
|
|
@ -339,6 +339,7 @@ fn paint_and_schedule(runner_ref: &AppRunnerRef, panicked: Arc<AtomicBool>) -> R
|
|||
fn paint_if_needed(runner_ref: &AppRunnerRef) -> Result<(), JsValue> {
|
||||
let mut runner_lock = runner_ref.lock();
|
||||
if runner_lock.needs_repaint.fetch_and_clear() {
|
||||
runner_lock.clear_color_buffer();
|
||||
let (needs_repaint, clipped_primitives) = runner_lock.logic()?;
|
||||
runner_lock.paint(&clipped_primitives)?;
|
||||
if needs_repaint {
|
||||
|
@ -509,11 +510,9 @@ fn install_document_events(runner_container: &AppRunnerContainer) -> Result<(),
|
|||
runner_container.add_event_listener(
|
||||
&document,
|
||||
"hashchange",
|
||||
|_: web_sys::Event, runner_lock| {
|
||||
let mut frame_lock = runner_lock.frame.lock();
|
||||
|
||||
|_: web_sys::Event, mut runner_lock| {
|
||||
// `epi::Frame::info(&self)` clones `epi::IntegrationInfo`, but we need to modify the original here
|
||||
if let Some(web_info) = &mut frame_lock.info.web_info {
|
||||
if let Some(web_info) = &mut runner_lock.frame.info.web_info {
|
||||
web_info.location.hash = location_hash();
|
||||
}
|
||||
},
|
||||
|
|
|
@ -7,6 +7,8 @@ All notable changes to the epaint crate will be documented in this file.
|
|||
* Removed the `single_threaded/multi_threaded` flags - epaint is now always thread-safe ([#1390](https://github.com/emilk/egui/pull/1390)).
|
||||
* `Tessellator::from_options` is now `Tessellator::new` ([#1408](https://github.com/emilk/egui/pull/1408)).
|
||||
* Renamed `TessellationOptions::anti_alias` to `feathering` ([#1408](https://github.com/emilk/egui/pull/1408)).
|
||||
* Renamed `AlphaImage` to `FontImage` to discourage any other use for it ([#1412](https://github.com/emilk/egui/pull/1412)).
|
||||
* Dark text is darker and more readable on bright backgrounds ([#1412](https://github.com/emilk/egui/pull/1412)).
|
||||
|
||||
|
||||
## 0.17.0 - 2022-02-22
|
||||
|
|
|
@ -6,21 +6,21 @@ use crate::Color32;
|
|||
///
|
||||
/// In order to paint the image on screen, you first need to convert it to
|
||||
///
|
||||
/// See also: [`ColorImage`], [`AlphaImage`].
|
||||
/// See also: [`ColorImage`], [`FontImage`].
|
||||
#[derive(Clone, PartialEq)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
|
||||
pub enum ImageData {
|
||||
/// RGBA image.
|
||||
Color(ColorImage),
|
||||
/// Used for the font texture.
|
||||
Alpha(AlphaImage),
|
||||
Font(FontImage),
|
||||
}
|
||||
|
||||
impl ImageData {
|
||||
pub fn size(&self) -> [usize; 2] {
|
||||
match self {
|
||||
Self::Color(image) => image.size,
|
||||
Self::Alpha(image) => image.size,
|
||||
Self::Font(image) => image.size,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -34,8 +34,7 @@ impl ImageData {
|
|||
|
||||
pub fn bytes_per_pixel(&self) -> usize {
|
||||
match self {
|
||||
Self::Color(_) => 4,
|
||||
Self::Alpha(_) => 1,
|
||||
Self::Color(_) | Self::Font(_) => 4,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -157,25 +156,28 @@ impl From<ColorImage> for ImageData {
|
|||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
/// An 8-bit image, representing difference levels of transparent white.
|
||||
/// A single-channel image designed for the font texture.
|
||||
///
|
||||
/// Used for the font texture
|
||||
#[derive(Clone, Default, Eq, Hash, PartialEq)]
|
||||
/// Each value represents "coverage", i.e. how much a texel is covered by a character.
|
||||
///
|
||||
/// This is roughly interpreted as the opacity of a white image.
|
||||
#[derive(Clone, Default, PartialEq)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
|
||||
pub struct AlphaImage {
|
||||
pub struct FontImage {
|
||||
/// width, height
|
||||
pub size: [usize; 2],
|
||||
/// The alpha (linear space 0-255) of something white.
|
||||
|
||||
/// The coverage value.
|
||||
///
|
||||
/// One byte per pixel. Often you want to use [`Self::srgba_pixels`] instead.
|
||||
pub pixels: Vec<u8>,
|
||||
/// Often you want to use [`Self::srgba_pixels`] instead.
|
||||
pub pixels: Vec<f32>,
|
||||
}
|
||||
|
||||
impl AlphaImage {
|
||||
impl FontImage {
|
||||
pub fn new(size: [usize; 2]) -> Self {
|
||||
Self {
|
||||
size,
|
||||
pixels: vec![0; size[0] * size[1]],
|
||||
pixels: vec![0.0; size[0] * size[1]],
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -194,24 +196,19 @@ impl AlphaImage {
|
|||
/// `gamma` should normally be set to 1.0.
|
||||
/// If you are having problems with text looking skinny and pixelated, try
|
||||
/// setting a lower gamma, e.g. `0.5`.
|
||||
pub fn srgba_pixels(
|
||||
&'_ self,
|
||||
gamma: f32,
|
||||
) -> impl ExactSizeIterator<Item = super::Color32> + '_ {
|
||||
let srgba_from_alpha_lut: Vec<Color32> = (0..=255)
|
||||
.map(|a| {
|
||||
let a = super::color::linear_f32_from_linear_u8(a).powf(gamma);
|
||||
super::Rgba::from_white_alpha(a).into()
|
||||
pub fn srgba_pixels(&'_ self, gamma: f32) -> impl ExactSizeIterator<Item = Color32> + '_ {
|
||||
self.pixels.iter().map(move |coverage| {
|
||||
// This is arbitrarily chosen to make text look as good as possible.
|
||||
// In particular, it looks good with gamma=1 and the default eframe backend,
|
||||
// which uses linear blending.
|
||||
// See https://github.com/emilk/egui/issues/1410
|
||||
let a = fast_round(coverage.powf(gamma / 2.2) * 255.0);
|
||||
Color32::from_rgba_premultiplied(a, a, a, a) // this makes no sense, but works
|
||||
})
|
||||
.collect();
|
||||
|
||||
self.pixels
|
||||
.iter()
|
||||
.map(move |&a| srgba_from_alpha_lut[a as usize])
|
||||
}
|
||||
|
||||
/// Clone a sub-region as a new image
|
||||
pub fn region(&self, [x, y]: [usize; 2], [w, h]: [usize; 2]) -> AlphaImage {
|
||||
/// Clone a sub-region as a new image.
|
||||
pub fn region(&self, [x, y]: [usize; 2], [w, h]: [usize; 2]) -> FontImage {
|
||||
assert!(x + w <= self.width());
|
||||
assert!(y + h <= self.height());
|
||||
|
||||
|
@ -221,40 +218,44 @@ impl AlphaImage {
|
|||
pixels.extend(&self.pixels[offset..(offset + w)]);
|
||||
}
|
||||
assert_eq!(pixels.len(), w * h);
|
||||
AlphaImage {
|
||||
FontImage {
|
||||
size: [w, h],
|
||||
pixels,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::Index<(usize, usize)> for AlphaImage {
|
||||
type Output = u8;
|
||||
impl std::ops::Index<(usize, usize)> for FontImage {
|
||||
type Output = f32;
|
||||
|
||||
#[inline]
|
||||
fn index(&self, (x, y): (usize, usize)) -> &u8 {
|
||||
fn index(&self, (x, y): (usize, usize)) -> &f32 {
|
||||
let [w, h] = self.size;
|
||||
assert!(x < w && y < h);
|
||||
&self.pixels[y * w + x]
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::IndexMut<(usize, usize)> for AlphaImage {
|
||||
impl std::ops::IndexMut<(usize, usize)> for FontImage {
|
||||
#[inline]
|
||||
fn index_mut(&mut self, (x, y): (usize, usize)) -> &mut u8 {
|
||||
fn index_mut(&mut self, (x, y): (usize, usize)) -> &mut f32 {
|
||||
let [w, h] = self.size;
|
||||
assert!(x < w && y < h);
|
||||
&mut self.pixels[y * w + x]
|
||||
}
|
||||
}
|
||||
|
||||
impl From<AlphaImage> for ImageData {
|
||||
impl From<FontImage> for ImageData {
|
||||
#[inline(always)]
|
||||
fn from(image: AlphaImage) -> Self {
|
||||
Self::Alpha(image)
|
||||
fn from(image: FontImage) -> Self {
|
||||
Self::Font(image)
|
||||
}
|
||||
}
|
||||
|
||||
fn fast_round(r: f32) -> u8 {
|
||||
(r + 0.5).floor() as _ // rust does a saturating cast since 1.45
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
/// A change to an image.
|
||||
|
|
|
@ -28,7 +28,7 @@ pub mod util;
|
|||
pub use {
|
||||
bezier::{CubicBezierShape, QuadraticBezierShape},
|
||||
color::{Color32, Rgba},
|
||||
image::{AlphaImage, ColorImage, ImageData, ImageDelta},
|
||||
image::{ColorImage, FontImage, ImageData, ImageDelta},
|
||||
mesh::{Mesh, Mesh16, Vertex},
|
||||
shadow::Shadow,
|
||||
shape::{
|
||||
|
|
|
@ -377,7 +377,7 @@ fn allocate_glyph(
|
|||
if v > 0.0 {
|
||||
let px = glyph_pos.0 + x as usize;
|
||||
let py = glyph_pos.1 + y as usize;
|
||||
image[(px, py)] = fast_round(v * 255.0);
|
||||
image[(px, py)] = v;
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -405,7 +405,3 @@ fn allocate_glyph(
|
|||
uv_rect,
|
||||
}
|
||||
}
|
||||
|
||||
fn fast_round(r: f32) -> u8 {
|
||||
(r + 0.5).floor() as _ // rust does a saturating cast since 1.45
|
||||
}
|
||||
|
|
|
@ -539,7 +539,7 @@ impl FontsImpl {
|
|||
// Make the top left pixel fully white:
|
||||
let (pos, image) = atlas.allocate((1, 1));
|
||||
assert_eq!(pos, (0, 0));
|
||||
image[pos] = 255;
|
||||
image[pos] = 1.0;
|
||||
}
|
||||
|
||||
let atlas = Arc::new(Mutex::new(atlas));
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
use crate::{AlphaImage, ImageDelta};
|
||||
use crate::{FontImage, ImageDelta};
|
||||
|
||||
#[derive(Clone, Copy, Eq, PartialEq)]
|
||||
struct Rectu {
|
||||
|
@ -32,7 +32,7 @@ impl Rectu {
|
|||
/// More characters can be added, possibly expanding the texture.
|
||||
#[derive(Clone)]
|
||||
pub struct TextureAtlas {
|
||||
image: AlphaImage,
|
||||
image: FontImage,
|
||||
/// What part of the image that is dirty
|
||||
dirty: Rectu,
|
||||
|
||||
|
@ -48,7 +48,7 @@ impl TextureAtlas {
|
|||
pub fn new(size: [usize; 2]) -> Self {
|
||||
assert!(size[0] >= 1024, "Tiny texture atlas");
|
||||
Self {
|
||||
image: AlphaImage::new(size),
|
||||
image: FontImage::new(size),
|
||||
dirty: Rectu::EVERYTHING,
|
||||
cursor: (0, 0),
|
||||
row_height: 0,
|
||||
|
@ -91,7 +91,7 @@ impl TextureAtlas {
|
|||
|
||||
/// Returns the coordinates of where the rect ended up,
|
||||
/// and invalidates the region.
|
||||
pub fn allocate(&mut self, (w, h): (usize, usize)) -> ((usize, usize), &mut AlphaImage) {
|
||||
pub fn allocate(&mut self, (w, h): (usize, usize)) -> ((usize, usize), &mut FontImage) {
|
||||
/// On some low-precision GPUs (my old iPad) characters get muddled up
|
||||
/// if we don't add some empty pixels between the characters.
|
||||
/// On modern high-precision GPUs this is not needed.
|
||||
|
@ -138,13 +138,13 @@ impl TextureAtlas {
|
|||
}
|
||||
}
|
||||
|
||||
fn resize_to_min_height(image: &mut AlphaImage, required_height: usize) -> bool {
|
||||
fn resize_to_min_height(image: &mut FontImage, required_height: usize) -> bool {
|
||||
while required_height >= image.height() {
|
||||
image.size[1] *= 2; // double the height
|
||||
}
|
||||
|
||||
if image.width() * image.height() > image.pixels.len() {
|
||||
image.pixels.resize(image.width() * image.height(), 0);
|
||||
image.pixels.resize(image.width() * image.height(), 0.0);
|
||||
true
|
||||
} else {
|
||||
false
|
||||
|
|
111
epi/src/lib.rs
111
epi/src/lib.rs
|
@ -15,8 +15,6 @@ pub mod file_storage;
|
|||
pub use egui; // Re-export for user convenience
|
||||
pub use glow; // Re-export for user convenience
|
||||
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
/// The is is how your app is created.
|
||||
///
|
||||
/// You can use the [`CreationContext`] to setup egui, restore state, setup OpenGL things, etc.
|
||||
|
@ -50,10 +48,10 @@ pub trait App {
|
|||
///
|
||||
/// Put your widgets into a [`egui::SidePanel`], [`egui::TopBottomPanel`], [`egui::CentralPanel`], [`egui::Window`] or [`egui::Area`].
|
||||
///
|
||||
/// The [`egui::Context`] and [`Frame`] can be cloned and saved if you like.
|
||||
/// The [`egui::Context`] can be cloned and saved if you like.
|
||||
///
|
||||
/// To force a repaint, call [`egui::Context::request_repaint`] at any time (e.g. from another thread).
|
||||
fn update(&mut self, ctx: &egui::Context, frame: &Frame);
|
||||
fn update(&mut self, ctx: &egui::Context, frame: &mut Frame);
|
||||
|
||||
/// Called on shutdown, and perhaps at regular intervals. Allows you to save state.
|
||||
///
|
||||
|
@ -250,78 +248,95 @@ pub struct IconData {
|
|||
///
|
||||
/// It provides methods to inspect the surroundings (are we on the web?),
|
||||
/// allocate textures, and change settings (e.g. window size).
|
||||
///
|
||||
/// [`Frame`] is cheap to clone and is safe to pass to other threads.
|
||||
#[derive(Clone)]
|
||||
pub struct Frame(pub Arc<Mutex<backend::FrameData>>);
|
||||
pub struct Frame {
|
||||
/// Information about the integration.
|
||||
#[doc(hidden)]
|
||||
pub info: IntegrationInfo,
|
||||
|
||||
/// Where the app can issue commands back to the integration.
|
||||
#[doc(hidden)]
|
||||
pub output: backend::AppOutput,
|
||||
|
||||
/// A place where you can store custom data in a way that persists when you restart the app.
|
||||
#[doc(hidden)]
|
||||
pub storage: Option<Box<dyn Storage>>,
|
||||
|
||||
/// A reference to the underlying [`glow`] (OpenGL) context.
|
||||
#[doc(hidden)]
|
||||
pub gl: std::rc::Rc<glow::Context>,
|
||||
}
|
||||
|
||||
impl Frame {
|
||||
/// Create a `Frame` - called by the integration.
|
||||
#[doc(hidden)]
|
||||
pub fn new(frame_data: backend::FrameData) -> Self {
|
||||
Self(Arc::new(Mutex::new(frame_data)))
|
||||
}
|
||||
|
||||
/// Access the underlying [`backend::FrameData`].
|
||||
#[doc(hidden)]
|
||||
#[inline]
|
||||
pub fn lock(&self) -> std::sync::MutexGuard<'_, backend::FrameData> {
|
||||
self.0.lock().unwrap()
|
||||
}
|
||||
|
||||
/// True if you are in a web environment.
|
||||
pub fn is_web(&self) -> bool {
|
||||
self.lock().info.web_info.is_some()
|
||||
self.info.web_info.is_some()
|
||||
}
|
||||
|
||||
/// Information about the integration.
|
||||
pub fn info(&self) -> IntegrationInfo {
|
||||
self.lock().info.clone()
|
||||
self.info.clone()
|
||||
}
|
||||
|
||||
/// A place where you can store custom data in a way that persists when you restart the app.
|
||||
pub fn storage(&self) -> Option<&dyn Storage> {
|
||||
self.storage.as_deref()
|
||||
}
|
||||
|
||||
/// A place where you can store custom data in a way that persists when you restart the app.
|
||||
pub fn storage_mut(&mut self) -> Option<&mut (dyn Storage + 'static)> {
|
||||
self.storage.as_deref_mut()
|
||||
}
|
||||
|
||||
/// A reference to the underlying [`glow`] (OpenGL) context.
|
||||
///
|
||||
/// This can be used, for instance, to:
|
||||
/// * Render things to offscreen buffers.
|
||||
/// * Read the pixel buffer from the previous frame (`glow::Context::read_pixels`).
|
||||
/// * Render things behind the egui windows.
|
||||
///
|
||||
/// Note that all egui painting is deferred to after the call to [`App::update`]
|
||||
/// ([`egui`] only collects [`egui::Shape`]s and then eframe paints them all in one go later on).
|
||||
pub fn gl(&self) -> &std::rc::Rc<glow::Context> {
|
||||
&self.gl
|
||||
}
|
||||
|
||||
/// Signal the app to stop/exit/quit the app (only works for native apps, not web apps).
|
||||
/// The framework will not quit immediately, but at the end of the this frame.
|
||||
pub fn quit(&self) {
|
||||
self.lock().output.quit = true;
|
||||
pub fn quit(&mut self) {
|
||||
self.output.quit = true;
|
||||
}
|
||||
|
||||
/// Set the desired inner size of the window (in egui points).
|
||||
pub fn set_window_size(&self, size: egui::Vec2) {
|
||||
self.lock().output.window_size = Some(size);
|
||||
pub fn set_window_size(&mut self, size: egui::Vec2) {
|
||||
self.output.window_size = Some(size);
|
||||
}
|
||||
|
||||
/// Set the desired title of the window.
|
||||
pub fn set_window_title(&self, title: &str) {
|
||||
self.lock().output.window_title = Some(title.to_owned());
|
||||
pub fn set_window_title(&mut self, title: &str) {
|
||||
self.output.window_title = Some(title.to_owned());
|
||||
}
|
||||
|
||||
/// Set whether to show window decorations (i.e. a frame around you app).
|
||||
/// If false it will be difficult to move and resize the app.
|
||||
pub fn set_decorations(&self, decorated: bool) {
|
||||
self.lock().output.decorated = Some(decorated);
|
||||
pub fn set_decorations(&mut self, decorated: bool) {
|
||||
self.output.decorated = Some(decorated);
|
||||
}
|
||||
|
||||
/// When called, the native window will follow the
|
||||
/// movement of the cursor while the primary mouse button is down.
|
||||
///
|
||||
/// Does not work on the web.
|
||||
pub fn drag_window(&self) {
|
||||
self.lock().output.drag_window = true;
|
||||
pub fn drag_window(&mut self) {
|
||||
self.output.drag_window = true;
|
||||
}
|
||||
|
||||
/// for integrations only: call once per frame
|
||||
pub fn take_app_output(&self) -> crate::backend::AppOutput {
|
||||
std::mem::take(&mut self.lock().output)
|
||||
#[doc(hidden)]
|
||||
pub fn take_app_output(&mut self) -> crate::backend::AppOutput {
|
||||
std::mem::take(&mut self.output)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[test]
|
||||
fn frame_impl_send_sync() {
|
||||
fn assert_send_sync<T: Send + Sync>() {}
|
||||
assert_send_sync::<Frame>();
|
||||
}
|
||||
|
||||
/// Information about the web environment (if applicable).
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct WebInfo {
|
||||
|
@ -464,20 +479,12 @@ pub const APP_KEY: &str = "app";
|
|||
// ----------------------------------------------------------------------------
|
||||
|
||||
/// You only need to look here if you are writing a backend for `epi`.
|
||||
#[doc(hidden)]
|
||||
pub mod backend {
|
||||
use super::*;
|
||||
|
||||
/// The data required by [`Frame`] each frame.
|
||||
pub struct FrameData {
|
||||
/// Information about the integration.
|
||||
pub info: IntegrationInfo,
|
||||
|
||||
/// Where the app can issue commands back to the integration.
|
||||
pub output: AppOutput,
|
||||
}
|
||||
|
||||
/// Action that can be taken by the user app.
|
||||
#[derive(Default)]
|
||||
#[derive(Clone, Debug, Default)]
|
||||
#[must_use]
|
||||
pub struct AppOutput {
|
||||
/// Set to `true` to stop the app.
|
||||
|
|
Loading…
Reference in a new issue