1
0
mirror of https://github.com/emilk/egui.git synced 2026-08-30 21:30:03 -04:00

Merge branch 'main' into lucas/allow-constructing-kittest-node

This commit is contained in:
Lucas Meurer
2026-07-23 11:59:28 +02:00
committed by GitHub
19 changed files with 583 additions and 670 deletions

View File

@@ -232,6 +232,7 @@ web-sys = { workspace = true, features = [
"File",
"FileList",
"FocusEvent",
"FocusOptions",
"HtmlCanvasElement",
"HtmlElement",
"HtmlInputElement",

View File

@@ -401,7 +401,7 @@ impl AppRunner {
} else {
// We are not editing text - give the focus to the canvas.
self.text_agent.blur();
self.canvas().focus().ok();
super::focus_without_scroll(self.canvas()).ok();
}
}

View File

@@ -599,7 +599,7 @@ fn install_pointerup(runner_ref: &WebRunner, target: &EventTarget) -> Result<(),
// not working when focusing on a text field in an egui app.
// This attempts to fix that by forcing the focus on any
// click on the canvas.
runner.canvas().focus().ok();
super::focus_without_scroll(runner.canvas()).ok();
// In Safari we are only allowed to do certain things
// (like playing audio, start a download, etc)

View File

@@ -84,6 +84,17 @@ pub(crate) fn has_focus<T: JsCast>(element: &T) -> bool {
try_has_focus(element).unwrap_or(false)
}
/// Focus the given element without scrolling it into view.
///
/// Scrolling the element into view would scroll the whole page when
/// the app is embedded in a larger scrollable page,
/// see <https://github.com/emilk/egui/issues/8295>.
pub(crate) fn focus_without_scroll(element: &web_sys::HtmlElement) -> Result<(), JsValue> {
let options = web_sys::FocusOptions::new();
options.set_prevent_scroll(true);
element.focus_with_options(&options)
}
/// Current time in seconds (since undefined point in time).
///
/// Monotonically increasing.

View File

@@ -4,7 +4,7 @@
use std::cell::Cell;
use wasm_bindgen::prelude::*;
use web_sys::{Document, Node};
use web_sys::Document;
use super::{AppRunner, WebRunner};
@@ -15,19 +15,23 @@ pub struct TextAgent {
impl TextAgent {
/// Attach the agent to the document.
pub fn attach(runner_ref: &WebRunner, root: Node) -> Result<Self, JsValue> {
pub fn attach(
runner_ref: &WebRunner,
canvas: &web_sys::HtmlCanvasElement,
) -> Result<Self, JsValue> {
let document = web_sys::window().unwrap().document().unwrap();
// create an `<input>` element
let input = document
.create_element("input")?
.dyn_into::<web_sys::HtmlElement>()?;
input.set_autofocus(true)?;
let input = input.dyn_into::<web_sys::HtmlInputElement>()?;
.dyn_into::<web_sys::HtmlInputElement>()?;
input.set_type("text");
input.set_attribute("autocapitalize", "off")?;
// append it to `<body>` and hide it outside of the viewport
// Hide the element, and park it over the canvas
// so that focusing it can never scroll some other part
// of the page into view.
let canvas_rect = super::canvas_content_rect(canvas);
let style = input.style();
style.set_property("background-color", "transparent")?;
style.set_property("border", "none")?;
@@ -36,11 +40,12 @@ impl TextAgent {
style.set_property("height", "1px")?;
style.set_property("caret-color", "transparent")?;
style.set_property("position", "absolute")?;
style.set_property("top", "0")?;
style.set_property("left", "0")?;
style.set_property("top", &format!("{}px", canvas_rect.min.y))?;
style.set_property("left", &format!("{}px", canvas_rect.min.x))?;
// Prevent auto-zoom on mobile browsers (requires at least 16px).
style.set_property("font-size", "16px")?;
let root = canvas.get_root_node();
if root.has_type::<Document>() {
// root object is a document, append to its body
root.dyn_into::<Document>()?
@@ -52,6 +57,13 @@ impl TextAgent {
root.append_child(&input)?;
}
// Focus the app on startup, without scrolling the page.
// We do this instead of setting the `autofocus` attribute,
// since the browser scrolls the focused element into view when
// honoring `autofocus`, and there is no way to prevent that.
// See https://github.com/emilk/egui/issues/8295
super::focus_without_scroll(&input).ok();
// attach event listeners
let on_input = {
@@ -67,7 +79,7 @@ impl TextAgent {
// between versions 14.7.09 and 17.0.12.
if !event.is_composing() {
input.blur().ok();
input.focus().ok();
super::focus_without_scroll(&input).ok();
}
if event.is_composing() {
@@ -221,7 +233,7 @@ impl TextAgent {
log::trace!("Focusing text agent");
if let Err(err) = self.input.focus() {
if let Err(err) = super::focus_without_scroll(&self.input) {
log::error!("failed to set focus: {}", super::string_from_js_value(&err));
}
}

View File

@@ -380,7 +380,7 @@ impl WebPainter for WebPainterWgpu {
);
}
frame.present();
render_state.queue.present(frame);
}
// Free textures marked for destruction **after** queue submit since they might still be used in the current frame.

View File

@@ -73,7 +73,7 @@ impl WebRunner {
{
// First set up the app runner:
let text_agent = TextAgent::attach(self, canvas.get_root_node())?;
let text_agent = TextAgent::attach(self, &canvas)?;
let app_runner =
AppRunner::new(canvas.clone(), web_options, app_creator, text_agent).await?;
self.app_runner.replace(Some(app_runner));

View File

@@ -212,10 +212,14 @@ impl CaptureState {
let buffer_slice = buffer.slice(..);
let mut pixels = Vec::with_capacity((tex_extent.width * tex_extent.height) as usize);
for padded_row in buffer_slice
.get_mapped_range()
.chunks(padding.padded_bytes_per_row as usize)
{
let mapped_range = match buffer_slice.get_mapped_range() {
Ok(range) => range,
Err(err) => {
log::error!("Failed to get mapped range for reading: {err}");
return;
}
};
for padded_row in mapped_range.chunks(padding.padded_bytes_per_row as usize) {
let row = &padded_row[..padding.unpadded_bytes_per_row as usize];
for color in row.chunks(4) {
pixels.push(epaint::Color32::from_rgba_premultiplied(

View File

@@ -150,6 +150,7 @@ async fn request_adapter(
// * fails if there's no software rasterizer available
// * can achieve the same with `native_adapter_selector`
force_fallback_adapter: false,
apply_limit_buckets: false,
})
.await
.inspect_err(|_err| {
@@ -473,6 +474,7 @@ pub fn adapter_info_summary(info: &wgpu::AdapterInfo) -> String {
subgroup_min_size,
subgroup_max_size,
transient_saves_memory,
limit_bucket,
} = &info;
// Example values:
@@ -523,9 +525,10 @@ pub fn adapter_info_summary(info: &wgpu::AdapterInfo) -> String {
}
write!(
summary,
", transient_saves_memory: {transient_saves_memory}"
", transient_saves_memory: {transient_saves_memory:?}"
)
.ok();
write!(summary, ", limit_bucket: {limit_bucket:?}").ok();
summary
}

View File

@@ -375,14 +375,14 @@ impl Renderer {
vertex: wgpu::VertexState {
entry_point: Some("vs_main"),
module: &module,
buffers: &[wgpu::VertexBufferLayout {
buffers: &[Some(wgpu::VertexBufferLayout {
array_stride: 5 * 4,
step_mode: wgpu::VertexStepMode::Vertex,
// 0: vec2 position
// 1: vec2 texture coordinates
// 2: uint color
attributes: &wgpu::vertex_attr_array![0 => Float32x2, 1 => Float32x2, 2 => Uint32],
}],
})],
compilation_options: wgpu::PipelineCompilationOptions::default()
},
primitive: wgpu::PrimitiveState {

View File

@@ -39,6 +39,7 @@ where
}
#[derive(Clone)]
#[expect(clippy::large_enum_variant)]
pub enum WgpuSetup {
/// Construct a wgpu setup using some predefined settings & heuristics.
/// This is the default option. You can customize most behaviours overriding the

View File

@@ -765,7 +765,7 @@ impl Painter {
profiling::scope!("present");
// wgpu doesn't document where vsync can happen. Maybe here?
let start = web_time::Instant::now();
output_frame.present();
render_state.queue.present(output_frame);
vsync_sec += start.elapsed().as_secs_f32();
}

View File

@@ -223,6 +223,7 @@ fn integration_ui(ui: &mut egui::Ui, _frame: &mut eframe::Frame) {
subgroup_min_size,
subgroup_max_size,
transient_saves_memory,
limit_bucket,
} = &info;
// Example values:
@@ -276,7 +277,11 @@ fn integration_ui(ui: &mut egui::Ui, _frame: &mut eframe::Frame) {
ui.end_row();
}
ui.label("Transient saves memory:");
ui.label(format!("{transient_saves_memory}"));
ui.label(format!("{transient_saves_memory:?}"));
ui.end_row();
ui.label("Limit bucket:");
ui.label(format!("{limit_bucket:?}"));
ui.end_row();
});
};

View File

@@ -58,7 +58,9 @@ pub(crate) fn texture_to_image(device: &Device, queue: &Queue, texture: &Texture
receiver.recv().unwrap().unwrap();
let buffer_slice = output_buffer.slice(..);
let data = buffer_slice.get_mapped_range();
let data = buffer_slice
.get_mapped_range()
.expect("Failed to get mapped range");
let data = data
.chunks_exact(buffer_dimensions.padded_bytes_per_row)
.flat_map(|row| row.iter().take(buffer_dimensions.unpadded_bytes_per_row))